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::collections::HashSet;
37use std::str::FromStr;
38
39pub mod error_types;
40pub mod future_views;
41pub mod security;
42
43pub use future_views::{HologramView, ParticleEmitter, StreamingText};
44
45// P1-13: extracted modules
46pub mod asset;
47pub mod dependency;
48pub mod error_boundary;
49pub mod knowledge;
50pub mod renderer;
51pub mod undo;
52pub mod virtual_list;
53pub mod window;
54
55// P1-13: re-exports for backward compatibility
56pub use asset::{AssetKey, AssetState, TokenValue, DesignTokens};
57pub use dependency::{DependencyGraph, FrameBudgetTracker, SubsystemBudget, InputLatencyTracker};
58pub use error_boundary::{ComponentErrorState, ErrorBoundary};
59pub use knowledge::{AnnouncementPriority, KnowledgeFragment, KnowledgeId, AppState, MemoryLayer, UiFidelityLevel, TemporalEdge, TemporalNode};
60pub use undo::{UndoGroup, UndoManager};
61pub use window::{Window, WindowCloseAction, WindowConfig, WindowHandle, WindowId, WindowLevel};
62
63pub trait View: Sized + Send {
64    /// The concrete type produced after applying modifiers.
65    /// For primitive views this is Self.
66    type Body: View;
67
68    fn body(self) -> Self::Body;
69
70    /// Render this view into the provided renderer at the specified bounds.
71    /// Primitive views override this to perform drawing operations.
72    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
73
74    /// Calculate the natural (intrinsic) size of this view given proposed constraints.
75    /// This allows views like Buttons or Labels to inform the layout engine of their needs.
76    fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
77        Size::ZERO
78    }
79
80    /// Optionally provide a layout implementation for this view.
81    fn layout(&self) -> Option<&dyn layout::LayoutView> {
82        None
83    }
84
85    /// Returns the flex weight of this view for proportional distribution in stacks.
86    fn flex_weight(&self) -> f32 {
87        0.0
88    }
89
90    /// Returns the grid placement configuration for this view if it is laid out in a Grid.
91    fn get_grid_placement(&self) -> Option<GridPlacement> {
92        None
93    }
94
95    /// Provided modifier entry point
96    fn modifier<M: ViewModifier>(self, m: M) -> ModifiedView<Self, M> {
97        ModifiedView::new(self, m)
98    }
99
100    /// Apply a Bifrost (Frosted Glass) effect to the view
101    fn bifrost(
102        self,
103        blur: f32,
104        saturation: f32,
105        opacity: f32,
106    ) -> ModifiedView<Self, FrostedGlassModifier> {
107        self.modifier(FrostedGlassModifier {
108            blur,
109            saturation,
110            opacity,
111            fresnel_strength: 1.0,
112        })
113    }
114
115    /// Apply a Bifrost (Frosted Glass) effect with full parameter control.
116    fn bifrost_full(
117        self,
118        blur: f32,
119        saturation: f32,
120        opacity: f32,
121        fresnel_strength: f32,
122    ) -> ModifiedView<Self, FrostedGlassModifier> {
123        self.modifier(FrostedGlassModifier {
124            blur,
125            saturation,
126            opacity,
127            fresnel_strength,
128        })
129    }
130
131    /// Apply a Gungnir (Neon Glow) effect to the view
132    fn gungnir(
133        self,
134        color: impl Into<String>,
135        radius: f32,
136        intensity: f32,
137    ) -> ModifiedView<Self, NeonGlowModifier> {
138        self.modifier(NeonGlowModifier {
139            color: color.into(),
140            radius,
141            intensity,
142        })
143    }
144
145    /// Apply a Mjolnir Slice (Geometric cut) to the view
146    fn mjolnir_slice(self, angle: f32, offset: f32) -> ModifiedView<Self, GeometricClipModifier> {
147        self.modifier(GeometricClipModifier { angle, offset })
148    }
149
150    /// Apply a Mjolnir Shatter (Fragmented transition) to the view
151    fn mjolnir_shatter(
152        self,
153        pieces: u32,
154        force: f32,
155    ) -> ModifiedView<Self, FragmentModifier> {
156        self.modifier(FragmentModifier { pieces, force })
157    }
158
159    /// Mark this view as a Bifrost Bridge (Shared Element) for cross-view persistence
160    fn bifrost_bridge(self, id: impl Into<String>) -> ModifiedView<Self, SharedElementModifier> {
161        self.modifier(SharedElementModifier { id: id.into() })
162    }
163
164    /// Add a background color to this view
165    fn background(self, color: [f32; 4]) -> ModifiedView<Self, BackgroundModifier> {
166        self.modifier(BackgroundModifier { color })
167    }
168
169    /// Add padding to this view
170    fn padding(self, amount: f32) -> ModifiedView<Self, PaddingModifier> {
171        self.modifier(PaddingModifier { amount })
172    }
173
174    /// Set the opacity (alpha) of this view in the range [0.0, 1.0].
175    fn opacity(self, opacity: f32) -> ModifiedView<Self, OpacityModifier> {
176        self.modifier(OpacityModifier {
177            opacity: opacity.clamp(0.0, 1.0),
178        })
179    }
180
181    /// Override the foreground (text / icon) color of this view.
182    fn foreground_color(self, color: [f32; 4]) -> ModifiedView<Self, ForegroundColorModifier> {
183        self.modifier(ForegroundColorModifier { color })
184    }
185
186    /// Constrain this view to an explicit width and/or height.
187    /// Constrains the size of this view using fixed width/height values.
188    fn frame(self, width: Option<f32>, height: Option<f32>) -> ModifiedView<Self, FrameModifier> {
189        self.modifier(FrameModifier {
190            width,
191            height,
192            min_width: None,
193            max_width: None,
194            min_height: None,
195            max_height: None,
196            alignment: Alignment::Center,
197        })
198    }
199
200    /// Give this view a flex weight for proportional space distribution in stacks.
201    fn flex(self, weight: f32) -> ModifiedView<Self, FlexModifier> {
202        self.modifier(FlexModifier { weight })
203    }
204
205    /// Specify the grid placement configuration (column, row, column_span, row_span) for this view.
206    fn grid_placement(self, placement: GridPlacement) -> ModifiedView<Self, GridPlacementModifier> {
207        self.modifier(GridPlacementModifier { placement })
208    }
209
210    /// Overlay a view on top of this view, aligned and offset relative to it.
211    fn overlay<O: View + Clone + 'static>(
212        self,
213        overlay: O,
214        alignment: Alignment,
215        offset: [f32; 2],
216        on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
217    ) -> ModifiedView<Self, OverlayModifier> {
218        self.modifier(OverlayModifier {
219            overlay: overlay.erase(),
220            alignment,
221            offset,
222            on_dismiss,
223        })
224    }
225
226    /// Automatically add padding to avoid overlapping with platform safe areas (notches, bars).
227    fn safe_area_padding(self) -> ModifiedView<Self, SafeAreaModifier> {
228        self.modifier(SafeAreaModifier { ignores: false })
229    }
230
231    /// Explicitly ignore platform safe areas and draw into the margins.
232    fn ignores_safe_area(self) -> ModifiedView<Self, SafeAreaModifier> {
233        self.modifier(SafeAreaModifier { ignores: true })
234    }
235
236    /// Clip all child drawing to this view's bounds.
237    fn clip_to_bounds(self) -> ModifiedView<Self, ClipModifier> {
238        self.modifier(ClipModifier)
239    }
240
241    /// Draw a colored border around this view.
242    fn border(self, color: [f32; 4], width: f32) -> ModifiedView<Self, BorderModifier> {
243        self.modifier(BorderModifier { color, width })
244    }
245
246    /// Add elevation (shadow) to the view. Level determines the shadow depth.
247    fn elevation(self, level: f32) -> ModifiedView<Self, ElevationModifier> {
248        self.modifier(ElevationModifier { level })
249    }
250
251    /// Apply an absolute-like position offset to this view.
252    /// The view is shifted by (x, y) from its layout position.
253    fn position(self, x: f32, y: f32) -> ModifiedView<Self, PositionModifier> {
254        self.modifier(PositionModifier { x, y })
255    }
256
257    /// Set the z-index (render order) for this view.
258    /// Higher values render on top of lower values.
259    fn z_index(self, z: i32) -> ModifiedView<Self, ZIndexModifier> {
260        self.modifier(ZIndexModifier { z_index: z })
261    }
262
263    /// Add a magnetic effect that pulls the view towards the cursor.
264    fn magnetic(self, radius: f32, intensity: f32) -> ModifiedView<Self, MagneticPullModifier> {
265        self.modifier(MagneticPullModifier { radius, intensity })
266    }
267
268    /// Add a ManiGlow (Lunar Illuminator) effect that glows near the cursor.
269    fn mani_glow(self, color: [f32; 4], radius: f32) -> ModifiedView<Self, CursorGlowModifier> {
270        self.modifier(CursorGlowModifier { color, radius })
271    }
272
273    /// Theme this view based on a specific memory layer.
274    fn memory_layer(self, layer: MemoryLayer) -> ModifiedView<Self, MemoryLayerModifier> {
275        self.modifier(MemoryLayerModifier { layer })
276    }
277
278    /// Enable Fafnir's Evolution: The component grows and glows as it is used.
279    fn fafnir_evolve(self, id: u64) -> ModifiedView<Self, EvolvingInteractionModifier> {
280        self.modifier(EvolvingInteractionModifier { id })
281    }
282
283    /// Enable Mimir's Intent: The component anticipates user interaction via pointer kinematics.
284    fn mimir_intent(self) -> ModifiedView<Self, IntentPredictionModifier> {
285        self.modifier(IntentPredictionModifier)
286    }
287
288    /// Enable Kvasir's Vibes: Subconscious telemetry representing cognitive complexity.
289    fn kvasir_vibes(self, complexity: f32) -> ModifiedView<Self, ComplexityTelemetryModifier> {
290        self.modifier(ComplexityTelemetryModifier { complexity })
291    }
292
293    /// Bestow Odin's Eye: Global omniscient observability layer.
294    fn odins_eye(self) -> ModifiedView<Self, ObservabilityOverlayModifier> {
295        self.modifier(ObservabilityOverlayModifier)
296    }
297
298    /// Trigger an action when the view appears
299    fn on_appear<F: Fn() + Send + Sync + 'static>(
300        self,
301        action: F,
302    ) -> ModifiedView<Self, LifecycleModifier> {
303        self.modifier(LifecycleModifier {
304            on_appear: Some(Arc::new(action)),
305            on_disappear: None,
306        })
307    }
308
309    /// Trigger an action when the view disappears
310    fn on_disappear<F: Fn() + Send + Sync + 'static>(
311        self,
312        action: F,
313    ) -> ModifiedView<Self, LifecycleModifier> {
314        self.modifier(LifecycleModifier {
315            on_appear: None,
316            on_disappear: Some(Arc::new(action)),
317        })
318    }
319
320    /// Trigger an action when the view is clicked
321    fn on_click<F: Fn() + Send + Sync + 'static>(
322        self,
323        action: F,
324    ) -> ModifiedView<Self, OnClickModifier> {
325        self.modifier(OnClickModifier {
326            action: Arc::new(action),
327        })
328    }
329
330    /// Trigger an action when the pointer enters the view bounds
331    fn on_pointer_enter<F: Fn() + Send + Sync + 'static>(
332        self,
333        action: F,
334    ) -> ModifiedView<Self, OnPointerEnterModifier> {
335        self.modifier(OnPointerEnterModifier {
336            action: Arc::new(action),
337        })
338    }
339
340    /// Trigger an action when the pointer leaves the view bounds
341    fn on_pointer_leave<F: Fn() + Send + Sync + 'static>(
342        self,
343        action: F,
344    ) -> ModifiedView<Self, OnPointerLeaveModifier> {
345        self.modifier(OnPointerLeaveModifier {
346            action: Arc::new(action),
347        })
348    }
349
350    /// Trigger an action when the pointer moves inside the view bounds
351    fn on_pointer_move<F: Fn(f32, f32) + Send + Sync + 'static>(
352        self,
353        action: F,
354    ) -> ModifiedView<Self, OnPointerMoveModifier> {
355        self.modifier(OnPointerMoveModifier {
356            action: Arc::new(action),
357        })
358    }
359
360    /// Trigger an action when the pointer is pressed down
361    fn on_pointer_down<F: Fn() + Send + Sync + 'static>(
362        self,
363        action: F,
364    ) -> ModifiedView<Self, OnPointerDownModifier> {
365        self.modifier(OnPointerDownModifier {
366            action: Arc::new(action),
367        })
368    }
369
370    /// Trigger an action when the pointer is released
371    fn on_pointer_up<F: Fn() + Send + Sync + 'static>(
372        self,
373        action: F,
374    ) -> ModifiedView<Self, OnPointerUpModifier> {
375        self.modifier(OnPointerUpModifier {
376            action: Arc::new(action),
377        })
378    }
379
380    /// Type-erase this view into AnyView
381    fn erase(self) -> AnyView
382    where
383        Self: Clone + 'static,
384    {
385        AnyView::new(self)
386    }
387
388    // =============================================================================
389    // ACCESSIBILITY
390    // =============================================================================
391
392    /// Return accessibility properties for this view.
393    /// Override to expose semantic role, label, state to assistive technology.
394    /// Default returns `None` (view is not explicitly accessible).
395    fn aria_properties(&self) -> Option<AriaProperties> {
396        None
397    }
398
399    /// Handle a keyboard navigation event.
400    /// Return true if consumed, false to bubble.
401    fn on_key_event(&self, _key: &str, _modifiers: KeyModifiers) -> bool {
402        false
403    }
404
405    /// Return keyboard shortcuts this view responds to.
406    fn key_shortcuts(&self) -> Vec<KeyShortcut> {
407        vec![]
408    }
409
410    /// Return true when this view's render output has changed since last build.
411    /// Default true for backward compatibility. Override for incremental skip.
412    /// When returns false, VDom::build skips this subtree entirely.
413    fn changed(&self) -> bool { true }
414
415    /// Stable identity for diff keying. Return None for anonymous views.
416    /// When Some(id), the VDOM layer uses this to match nodes across rebuilds,
417    /// enabling handler survival and incremental patch generation.
418    fn view_id(&self) -> Option<u64> { None }
419}
420
421// =============================================================================
422// ARIA PROPERTIES
423// =============================================================================
424
425/// Semantic role for assistive technology (WCAG 2.1 §4.1.2).
426#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
427pub enum AriaRole {
428    Alert,
429    Alertdialog,
430    Article,
431    Banner,
432    Button,
433    Checkbox,
434    Columnheader,
435    Combobox,
436    Complementary,
437    Contentinfo,
438    Dialog,
439    Form,
440    Grid,
441    Gridcell,
442    Heading,
443    Img,
444    Link,
445    List,
446    Listbox,
447    Listitem,
448    Main,
449    Menu,
450    Menubar,
451    Menuitem,
452    Menuitemcheckbox,
453    Menuitemradio,
454    Navigation,
455    None,
456    Note,
457    Option,
458    Presentation,
459    Progressbar,
460    Radio,
461    Radiogroup,
462    Region,
463    Row,
464    Rowgroup,
465    Rowheader,
466    Search,
467    Separator,
468    Slider,
469    Spinbutton,
470    Status,
471    Switch,
472    Tab,
473    Table,
474    Tablist,
475    Tabpanel,
476    Textbox,
477    Toolbar,
478    Tooltip,
479    Tree,
480    Treeitem,
481}
482
483/// Accessible properties for a view, describing its semantic role and state.
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct AriaProperties {
486    pub role: AriaRole,
487    pub label: String,
488    pub description: Option<String>,
489    pub value: Option<String>,
490    pub pressed: Option<bool>,
491    pub checked: Option<bool>,
492    pub expanded: Option<bool>,
493    pub disabled: bool,
494    pub hidden: bool,
495    pub level: Option<u8>,
496    pub shortcut: Option<String>,
497    pub focused: bool,
498    pub live: Option<String>,
499    pub atomic: bool,
500}
501
502impl AriaProperties {
503    pub fn new(role: AriaRole, label: impl Into<String>) -> Self {
504        Self {
505            role,
506            label: label.into(),
507            description: None,
508            value: None,
509            pressed: None,
510            checked: None,
511            expanded: None,
512            disabled: false,
513            hidden: false,
514            level: None,
515            shortcut: None,
516            focused: false,
517            live: None,
518            atomic: false,
519        }
520    }
521
522    pub fn description(mut self, d: impl Into<String>) -> Self {
523        self.description = Some(d.into());
524        self
525    }
526    pub fn value(mut self, v: impl Into<String>) -> Self {
527        self.value = Some(v.into());
528        self
529    }
530    pub fn checked(mut self, c: bool) -> Self {
531        self.checked = Some(c);
532        self
533    }
534    pub fn disabled(mut self, d: bool) -> Self {
535        self.disabled = d;
536        self
537    }
538    pub fn expanded(mut self, e: bool) -> Self {
539        self.expanded = Some(e);
540        self
541    }
542    pub fn level(mut self, l: u8) -> Self {
543        self.level = Some(l.clamp(1, 6));
544        self
545    }
546    pub fn shortcut(mut self, s: impl Into<String>) -> Self {
547        self.shortcut = Some(s.into());
548        self
549    }
550    pub fn focused(mut self, f: bool) -> Self {
551        self.focused = f;
552        self
553    }
554}
555
556// =============================================================================
557// KEYBOARD NAVIGATION
558// =============================================================================
559
560/// Modifier keys for keyboard events.
561#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
562pub struct KeyModifiers {
563    pub shift: bool,
564    pub ctrl: bool,
565    pub alt: bool,
566    pub meta: bool,
567}
568
569/// A keyboard shortcut binding.
570#[derive(Debug, Clone, Serialize, Deserialize)]
571pub struct KeyShortcut {
572    pub key: String,
573    pub modifiers: KeyModifiers,
574    pub description: String,
575}
576
577impl KeyShortcut {
578    pub fn new(key: impl Into<String>, desc: impl Into<String>) -> Self {
579        Self {
580            key: key.into(),
581            modifiers: KeyModifiers::default(),
582            description: desc.into(),
583        }
584    }
585    pub fn with_ctrl(mut self) -> Self {
586        self.modifiers.ctrl = true;
587        self
588    }
589    pub fn with_shift(mut self) -> Self {
590        self.modifiers.shift = true;
591        self
592    }
593    pub fn with_alt(mut self) -> Self {
594        self.modifiers.alt = true;
595        self
596    }
597    pub fn with_meta(mut self) -> Self {
598        self.modifiers.meta = true;
599        self
600    }
601}
602
603// =============================================================================
604// FOCUS MANAGEMENT
605// =============================================================================
606
607/// Unique ID for a focusable element.
608#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
609pub struct FocusableId(String);
610
611impl FocusableId {
612    /// Returns the inner string representation of the focusable ID.
613    pub fn as_str(&self) -> &str {
614        &self.0
615    }
616}
617
618impl From<&str> for FocusableId {
619    fn from(s: &str) -> Self {
620        Self(s.to_string())
621    }
622}
623impl From<String> for FocusableId {
624    fn from(s: String) -> Self {
625        Self(s)
626    }
627}
628
629/// Focus trap for confining Tab navigation (e.g., modals).
630#[derive(Debug, Clone)]
631pub struct FocusTrap {
632    pub id: FocusableId,
633    pub order: Vec<FocusableId>,
634    pub wrap: bool,
635}
636
637impl FocusTrap {
638    pub fn new(id: impl Into<FocusableId>, order: Vec<FocusableId>) -> Self {
639        Self {
640            id: id.into(),
641            order,
642            wrap: true,
643        }
644    }
645}
646
647/// Manages focus order, Tab/Shift+Tab navigation, and focus traps.
648#[derive(Debug, Default)]
649pub struct FocusManager {
650    order: Vec<FocusableId>,
651    order_set: HashSet<FocusableId>,
652    focused: Option<FocusableId>,
653    traps: Vec<FocusTrap>,
654}
655
656impl FocusManager {
657    pub fn new() -> Self {
658        Self::default()
659    }
660
661    pub fn register(&mut self, id: impl Into<FocusableId>) {
662        let id = id.into();
663        if self.order_set.insert(id.clone()) {
664            self.order.push(id);
665        }
666    }
667
668    pub fn unregister(&mut self, id: &FocusableId) {
669        if self.order_set.remove(id) {
670            self.order.retain(|x| x != id);
671        }
672        if self.focused.as_ref() == Some(id) {
673            self.focused = None;
674        }
675    }
676
677    pub fn focused(&self) -> Option<&FocusableId> {
678        self.focused.as_ref()
679    }
680
681    pub fn focus(&mut self, id: impl Into<FocusableId>) -> bool {
682        let id = id.into();
683        if self.order.contains(&id) || self.traps.iter().any(|t| t.order.contains(&id)) {
684            self.focused = Some(id);
685            true
686        } else {
687            false
688        }
689    }
690
691    pub fn focus_next(&mut self) -> Option<&FocusableId> {
692        let order = self.effective_order();
693        if order.is_empty() {
694            return None;
695        }
696        let idx = self
697            .focused
698            .as_ref()
699            .and_then(|f| order.iter().position(|x| x == f));
700        let next = match idx {
701            Some(i) if i + 1 < order.len() => &order[i + 1],
702            _ => &order[0],
703        };
704        self.focused = Some(next.clone());
705        self.focused.as_ref()
706    }
707
708    pub fn focus_prev(&mut self) -> Option<&FocusableId> {
709        let order = self.effective_order();
710        if order.is_empty() {
711            return None;
712        }
713        let idx = self
714            .focused
715            .as_ref()
716            .and_then(|f| order.iter().position(|x| x == f));
717        let prev = match idx {
718            Some(i) if i > 0 => &order[i - 1],
719            _ => &order[order.len() - 1],
720        };
721        self.focused = Some(prev.clone());
722        self.focused.as_ref()
723    }
724
725    pub fn push_trap(&mut self, trap: FocusTrap) -> FocusableId {
726        let id = trap.id.clone();
727        self.traps.push(trap);
728        id
729    }
730
731    pub fn pop_trap(&mut self) {
732        self.traps.pop();
733    }
734    pub fn trap_count(&self) -> usize {
735        self.traps.len()
736    }
737
738    fn effective_order(&self) -> &[FocusableId] {
739        self.traps
740            .last()
741            .map(|t| t.order.as_slice())
742            .unwrap_or(&self.order)
743    }
744}
745
746// =============================================================================
747// REDUCED MOTION
748// =============================================================================
749
750/// Detects OS-level reduced motion preference via [`AccessibilityPreferences`].
751///
752/// This delegates to `AccessibilityPreferences::detect_from_system()` which
753/// queries the correct OS API on macOS, Linux, and Windows.
754pub fn is_reduced_motion() -> bool {
755    AccessibilityPreferences::detect_from_system().reduce_motion
756}
757
758/// Returns effective animation duration (0.0 if reduced motion is active).
759pub fn effective_duration(secs: f32) -> f32 {
760    if is_reduced_motion() { 0.0 } else { secs }
761}
762
763/// An object-safe version of the View trait for type erasure.
764pub trait ErasedView: Send {
765    fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect);
766    fn name(&self) -> &'static str;
767    fn flex_weight_erased(&self) -> f32;
768    fn layout_erased(&self) -> Option<&dyn layout::LayoutView>;
769    fn grid_placement_erased(&self) -> Option<GridPlacement>;
770    fn clone_box(&self) -> Box<dyn ErasedView>;
771}
772
773impl<V: View + Clone + 'static> ErasedView for V {
774    fn render_erased(&self, renderer: &mut dyn Renderer, rect: Rect) {
775        self.render(renderer, rect);
776    }
777
778    fn name(&self) -> &'static str {
779        std::any::type_name::<V>()
780    }
781
782    fn flex_weight_erased(&self) -> f32 {
783        self.flex_weight()
784    }
785
786    fn layout_erased(&self) -> Option<&dyn layout::LayoutView> {
787        self.layout()
788    }
789
790    fn grid_placement_erased(&self) -> Option<GridPlacement> {
791        self.get_grid_placement()
792    }
793
794    fn clone_box(&self) -> Box<dyn ErasedView> {
795        Box::new(self.clone())
796    }
797}
798
799/// A view that memoizes its rendering based on a stable ID and data hash.
800/// The renderer can use this to skip re-rendering the sub-tree if the data hasn't changed.
801pub struct MemoView<V, F> {
802    id: u64,
803    data_hash: u64,
804    builder: F,
805    _v: std::marker::PhantomData<V>,
806}
807
808impl<V: View, F: Fn() -> V + Send + Sync> MemoView<V, F> {
809    /// Create a new MemoView with a stable ID and a data hash.
810    pub fn new(id: u64, data_hash: u64, builder: F) -> Self {
811        Self {
812            id,
813            data_hash,
814            builder,
815            _v: std::marker::PhantomData,
816        }
817    }
818}
819
820impl<V: View + 'static, F: Fn() -> V + Send + Sync + 'static> View for MemoView<V, F> {
821    type Body = Never;
822    fn body(self) -> Self::Body {
823        // SAFETY: `Never` is uninhabitable (zero variants). MemoView renders via
824        // `render()` using the memoized builder closure and never exposes a body.
825        unreachable!("MemoView does not have a body")
826    }
827
828    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
829        renderer.memoize(self.id, self.data_hash, &|r| {
830            let view = (self.builder)();
831            view.render(r, rect);
832        });
833    }
834}
835
836/// A type-erased View wrapper.
837pub struct AnyView {
838    inner: Box<dyn ErasedView>,
839}
840
841impl Clone for AnyView {
842    fn clone(&self) -> Self {
843        Self {
844            inner: self.inner.clone_box(),
845        }
846    }
847}
848
849impl AnyView {
850    pub fn new<V: View + Clone + 'static>(view: V) -> Self {
851        Self {
852            inner: Box::new(view),
853        }
854    }
855}
856
857impl View for AnyView {
858    type Body = Never;
859    fn body(self) -> Self::Body {
860        // SAFETY: `Never` is uninhabitable. AnyView is a type-erased wrapper that
861        // renders via `render_erased()` and never exposes a composable body.
862        unreachable!()
863    }
864
865    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
866        renderer.push_vnode(rect, self.inner.name());
867        self.inner.render_erased(renderer, rect);
868        renderer.pop_vnode();
869    }
870
871    fn flex_weight(&self) -> f32 {
872        self.inner.flex_weight_erased()
873    }
874
875    fn layout(&self) -> Option<&dyn layout::LayoutView> {
876        self.inner.layout_erased()
877    }
878
879    fn get_grid_placement(&self) -> Option<GridPlacement> {
880        self.inner.grid_placement_erased()
881    }
882}
883
884/// SharedElementModifier enables shared-element transitions.
885/// When two views share the same Bifrost Bridge ID, the Sleipnir solver will
886/// interpolate their geometry and effects (blur, glow) during the transition.
887#[derive(Debug, Clone, PartialEq)]
888pub struct SharedElementModifier {
889    pub id: String,
890}
891
892impl ViewModifier for SharedElementModifier {
893    fn modify<V: View>(self, content: V) -> impl View {
894        ModifiedView::new(content, self)
895    }
896
897    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
898        // Register this element with the renderer for shared-element transition logic
899        renderer.register_shared_element(&self.id, rect);
900    }
901}
902
903/// GeometricClipModifier implements the "Geometric Slice" aesthetic.
904/// It uses a signed distance field (SDF) to clip the view along a sharp angled line.
905#[derive(Debug, Clone, Copy, PartialEq)]
906pub struct GeometricClipModifier {
907    pub angle: f32,
908    pub offset: f32,
909}
910
911impl ViewModifier for GeometricClipModifier {
912    fn modify<V: View>(self, content: V) -> impl View {
913        ModifiedView::new(content, self)
914    }
915
916    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
917        renderer.push_mjolnir_slice(self.angle, self.offset);
918    }
919
920    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
921        renderer.pop_mjolnir_slice();
922    }
923}
924
925/// FragmentModifier implements the "Shattering" effect.
926/// It breaks the view into discrete geometric fragments that can be animated.
927#[derive(Debug, Clone, Copy, PartialEq)]
928pub struct FragmentModifier {
929    pub pieces: u32,
930    pub force: f32,
931}
932
933impl ViewModifier for FragmentModifier {
934    fn modify<V: View>(self, content: V) -> impl View {
935        ModifiedView::new(content, self)
936    }
937
938    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
939        // RADIAL SHATTER: Fragment the view into wedges
940        let pieces = self.pieces.max(1);
941        for i in 0..pieces {
942            let progress = i as f32 / pieces as f32;
943            let next_progress = (i + 1) as f32 / pieces as f32;
944
945            let angle_start = progress * 360.0;
946            let angle_end = next_progress * 360.0;
947
948            // Wedge slice: intersection of two half-planes
949            renderer.push_mjolnir_slice(angle_start, 0.0);
950            renderer.push_mjolnir_slice(angle_end + 180.0, 0.0);
951
952            // Apply radial force offset
953            let mid_angle = (angle_start + angle_end) / 2.0;
954            let rad = mid_angle.to_radians();
955            let dx = rad.cos() * self.force;
956            let dy = rad.sin() * self.force;
957
958            let shard_rect = Rect {
959                x: rect.x + dx,
960                y: rect.y + dy,
961                ..rect
962            };
963
964            view.render(renderer, shard_rect);
965
966            renderer.pop_mjolnir_slice();
967            renderer.pop_mjolnir_slice();
968        }
969    }
970}
971
972/// FrostedGlassModifier implements the Cyberpunk "Frosted Glass" aesthetic.
973/// It triggers backdrop blurring and light scattering in the render pipeline.
974#[derive(Debug, Clone, Copy, PartialEq)]
975pub struct FrostedGlassModifier {
976    pub blur: f32,
977    pub saturation: f32,
978    pub opacity: f32,
979    /// Fresnel strength multiplier. 0.0 = no fresnel, 1.0 = full.
980    pub fresnel_strength: f32,
981}
982
983impl ViewModifier for FrostedGlassModifier {
984    fn modify<V: View>(self, content: V) -> impl View {
985        ModifiedView::new(content, self)
986    }
987
988    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
989        if renderer.is_over_budget() {
990            // Degrade: Use lower quality (half blur) if over budget
991            renderer.bifrost(rect, self.blur * 0.5, self.saturation, self.opacity);
992        } else {
993            renderer.bifrost(rect, self.blur, self.saturation, self.opacity);
994        }
995    }
996}
997
998/// A modifier that adds a background color to a view.
999#[derive(Debug, Clone, Copy, PartialEq)]
1000pub struct BackgroundModifier {
1001    pub color: [f32; 4],
1002}
1003
1004impl ViewModifier for BackgroundModifier {
1005    fn modify<V: View>(self, content: V) -> impl View {
1006        ModifiedView::new(content, self)
1007    }
1008
1009    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1010        renderer.fill_rect(rect, self.color);
1011    }
1012}
1013
1014/// A modifier that adds padding to a view.
1015#[derive(Debug, Clone, Copy, PartialEq)]
1016pub struct PaddingModifier {
1017    pub amount: f32,
1018}
1019
1020impl ViewModifier for PaddingModifier {
1021    fn modify<V: View>(self, content: V) -> impl View {
1022        ModifiedView::new(content, self)
1023    }
1024
1025    fn transform_rect(&self, rect: Rect) -> Rect {
1026        Rect {
1027            x: rect.x + self.amount,
1028            y: rect.y + self.amount,
1029            width: (rect.width - 2.0 * self.amount).max(0.0),
1030            height: (rect.height - 2.0 * self.amount).max(0.0),
1031        }
1032    }
1033
1034    fn transform_proposal(&self, mut proposal: SizeProposal) -> SizeProposal {
1035        if let Some(w) = proposal.width {
1036            proposal.width = Some((w - 2.0 * self.amount).max(0.0));
1037        }
1038        if let Some(h) = proposal.height {
1039            proposal.height = Some((h - 2.0 * self.amount).max(0.0));
1040        }
1041        proposal
1042    }
1043
1044    fn transform_size(&self, mut size: Size) -> Size {
1045        size.width += 2.0 * self.amount;
1046        size.height += 2.0 * self.amount;
1047        size
1048    }
1049}
1050
1051/// NeonGlowModifier implements the "Neon Glow" aesthetic.
1052/// It uses additive blending and multi-pass blurring to simulate glowing light.
1053#[derive(Debug, Clone, PartialEq)]
1054pub struct NeonGlowModifier {
1055    pub color: String,
1056    pub radius: f32,
1057    pub intensity: f32,
1058}
1059
1060impl ViewModifier for NeonGlowModifier {
1061    fn modify<V: View>(self, content: V) -> impl View {
1062        ModifiedView::new(content, self)
1063    }
1064
1065    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1066        // Neon Glow using Mode 1 in the Surtr pipeline
1067        renderer.stroke_rect(rect, [0.0, 1.0, 1.0, self.intensity], self.radius / 10.0);
1068    }
1069}
1070
1071/// PulsingGlowModifier implements a "breathing" neon effect.
1072#[derive(Debug, Clone, Copy, PartialEq)]
1073pub struct PulsingGlowModifier {
1074    pub color: [f32; 4],
1075    pub radius: f32,
1076    pub speed: f32,
1077}
1078
1079impl ViewModifier for PulsingGlowModifier {
1080    fn modify<V: View>(self, content: V) -> impl View {
1081        ModifiedView::new(content, self)
1082    }
1083
1084    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1085        let time = std::time::SystemTime::now()
1086            .duration_since(std::time::UNIX_EPOCH)
1087            .unwrap_or_default()
1088            .as_secs_f32();
1089
1090        // Mode 19: Dashed Border
1091        // Mode 20: 9-Slice / Patch Scaling
1092        let intensity = (time * self.speed).sin() * 0.5 + 0.5;
1093        let mut color = self.color;
1094        color[3] *= intensity;
1095
1096        // Mode 1 neon glow with dynamic intensity
1097        renderer.stroke_rect(rect, color, self.radius);
1098    }
1099}
1100
1101/// MagneticPullModifier makes a view "magnetic", subtly leaning towards or pulling the cursor.
1102#[derive(Debug, Clone, Copy, PartialEq)]
1103pub struct MagneticPullModifier {
1104    pub radius: f32,
1105    pub intensity: f32,
1106}
1107
1108impl ViewModifier for MagneticPullModifier {
1109    fn modify<V: View>(self, content: V) -> impl View {
1110        ModifiedView::new(content, self)
1111    }
1112
1113    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1114        let [px, py] = renderer.get_pointer_position();
1115        let center_x = rect.x + rect.width / 2.0;
1116        let center_y = rect.y + rect.height / 2.0;
1117
1118        let dx = px - center_x;
1119        let dy = py - center_y;
1120        let dist = (dx * dx + dy * dy).sqrt();
1121
1122        let mut offset_x = 0.0;
1123        let mut offset_y = 0.0;
1124
1125        if dist < self.radius && dist > 0.0 {
1126            let force = (1.0 - dist / self.radius) * self.intensity;
1127            offset_x = dx * force;
1128            offset_y = dy * force;
1129        }
1130
1131        let magnetic_rect = Rect {
1132            x: rect.x + offset_x,
1133            y: rect.y + offset_y,
1134            ..rect
1135        };
1136
1137        view.render(renderer, magnetic_rect);
1138    }
1139}
1140
1141/// CursorGlowModifier adds a soft, lunar-like cursor glow to a view.
1142/// Named after Máni, the personification of the Moon.
1143#[derive(Debug, Clone, Copy, PartialEq)]
1144pub struct CursorGlowModifier {
1145    pub color: [f32; 4],
1146    pub radius: f32,
1147}
1148
1149impl ViewModifier for CursorGlowModifier {
1150    fn modify<V: View>(self, content: V) -> impl View {
1151        ModifiedView::new(content, self)
1152    }
1153
1154    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1155        if crate::load_system_state().realm == UiFidelityLevel::Asgard {
1156            renderer.mani_glow(rect, self.color, self.radius);
1157        }
1158        view.render(renderer, rect);
1159    }
1160}
1161
1162/// MemoryLayerModifier themes a view based on its cognitive memory layer.
1163/// Episodic: Shifting aurora clouds.
1164/// Semantic: Crystalline gold.
1165/// Procedural: Heavy obsidian stone.
1166#[derive(Debug, Clone, Copy, PartialEq)]
1167pub struct MemoryLayerModifier {
1168    pub layer: MemoryLayer,
1169}
1170
1171impl ViewModifier for MemoryLayerModifier {
1172    fn modify<V: View>(self, content: V) -> impl View {
1173        ModifiedView::new(content, self)
1174    }
1175
1176    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1177        let realm = crate::load_system_state().realm;
1178        match self.layer {
1179            MemoryLayer::Episodic => {
1180                if realm == UiFidelityLevel::Asgard {
1181                    renderer.bifrost(rect, 40.0, 1.2, 0.7);
1182                } else {
1183                    renderer.fill_rect(rect, [0.1, 0.12, 0.15, 0.8]);
1184                }
1185            }
1186            MemoryLayer::Semantic => {
1187                if realm == UiFidelityLevel::Asgard {
1188                    renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0, 0.6);
1189                } else {
1190                    renderer.stroke_rect(rect, [0.4, 0.4, 0.4, 1.0], 1.5);
1191                }
1192            }
1193            MemoryLayer::Procedural => {
1194                renderer.fill_rect(rect, [0.05, 0.05, 0.07, 0.95]);
1195                let stroke_color = if realm == UiFidelityLevel::Asgard {
1196                    [0.3, 0.3, 0.3, 1.0]
1197                } else {
1198                    [0.2, 0.2, 0.2, 1.0]
1199                };
1200                renderer.stroke_rect(rect, stroke_color, 2.0);
1201            }
1202        }
1203        view.render(renderer, rect);
1204    }
1205}
1206
1207/// EvolvingInteractionModifier enables self-evolving UI capabilities.
1208/// Named after Fafnir, the dragon who grows in power based on the gold he hoards.
1209/// In CVKG, 'Gold' is user attention/interaction.
1210#[derive(Debug, Clone, Copy, PartialEq)]
1211pub struct EvolvingInteractionModifier {
1212    /// Unique ID for tracking this component's vitality across frames.
1213    pub id: u64,
1214}
1215
1216impl ViewModifier for EvolvingInteractionModifier {
1217    fn modify<V: View>(self, content: V) -> impl View {
1218        ModifiedView::new(content, self)
1219    }
1220
1221    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1222        let state = crate::load_system_state();
1223        let vitality = state
1224            .get_component_state::<f32>(self.id)
1225            .map(|v| *v.read().unwrap())
1226            .unwrap_or(1.0);
1227
1228        // Calculate evolutionary growth factors
1229        // Max growth at vitality 5.0 (50% scale increase, strong glow)
1230        let growth = (vitality - 1.0).clamp(0.0, 4.0);
1231        let scale = 1.0 + growth * 0.12;
1232        let glow_intensity = growth * 0.25;
1233
1234        // Feed Fafnir: Register interaction to boost vitality
1235        let id = self.id;
1236        renderer.register_handler(
1237            "pointermove",
1238            std::sync::Arc::new(move |_| {
1239                crate::update_system_state(|s| {
1240                    let mut s = s.clone();
1241                    let v = s
1242                        .get_component_state::<f32>(id)
1243                        .map(|v| *v.read().unwrap())
1244                        .unwrap_or(1.0);
1245                    s.set_component_state(id, (v + 0.05).min(5.0)); // Cap at 5.0
1246                    s
1247                });
1248            }),
1249        );
1250
1251        if scale > 1.01 {
1252            renderer.push_transform([0.0, 0.0], [scale, scale], 0.0);
1253        }
1254
1255        if glow_intensity > 0.1 && state.realm == UiFidelityLevel::Asgard {
1256            renderer.gungnir(rect, [1.0, 0.84, 0.0, 1.0], 15.0 * vitality, glow_intensity);
1257        }
1258
1259        view.render(renderer, rect);
1260
1261        if scale > 1.01 {
1262            renderer.pop_transform();
1263        }
1264    }
1265}
1266
1267/// IntentPredictionModifier anticipates user movement and manifests holographic ghosts.
1268#[derive(Debug, Clone, Copy, PartialEq)]
1269pub struct IntentPredictionModifier;
1270
1271impl ViewModifier for IntentPredictionModifier {
1272    fn modify<V: View>(self, content: V) -> impl View {
1273        ModifiedView::new(content, self)
1274    }
1275
1276    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1277        let state = crate::load_system_state();
1278        let pos = state.last_pointer_pos;
1279        let vel = state.pointer_velocity;
1280
1281        // Calculate if the cursor is moving towards this rect
1282        let center = [rect.x + rect.width / 2.0, rect.y + rect.height / 2.0];
1283        let dx = center[0] - pos[0];
1284        let dy = center[1] - pos[1];
1285
1286        // Dot product of velocity and direction to center
1287        let dot = vel[0] * dx + vel[1] * dy;
1288        let speed_sq = vel[0] * vel[0] + vel[1] * vel[1];
1289        let dist_sq = dx * dx + dy * dy;
1290
1291        if dot > 0.0 && dist_sq < 250.0 * 250.0 && speed_sq > 0.5 && state.realm == UiFidelityLevel::Asgard {
1292            // Intent detected: render a subtle "ghost" reveal
1293            let intent_strength = (dot / (speed_sq.sqrt() * dist_sq.sqrt())).clamp(0.0, 1.0);
1294            renderer.stroke_rect(rect, [0.0, 0.9, 1.0, 0.3 * intent_strength], 1.5);
1295        }
1296
1297        view.render(renderer, rect);
1298    }
1299}
1300
1301/// ComplexityTelemetryModifier renders a cognitive telemetry cloud representing agent complexity.
1302#[derive(Debug, Clone, Copy, PartialEq)]
1303pub struct ComplexityTelemetryModifier {
1304    pub complexity: f32,
1305}
1306
1307impl ViewModifier for ComplexityTelemetryModifier {
1308    fn modify<V: View>(self, content: V) -> impl View {
1309        ModifiedView::new(content, self)
1310    }
1311
1312    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1313        if crate::load_system_state().realm == UiFidelityLevel::Asgard {
1314            let t = renderer.elapsed_time();
1315            let c = self.complexity.clamp(0.0, 1.0);
1316
1317            // 1. Core Cognitive Cloud (Bifrost)
1318            // Turbulence increases with complexity
1319            let blur = 20.0 + c * 40.0;
1320            let turbulence_x = (t * (1.0 + c * 2.0)).sin() * 8.0 * c;
1321            let turbulence_y = (t * (0.8 + c * 1.5)).cos() * 5.0 * c;
1322            renderer.bifrost(
1323                rect.offset(turbulence_x, turbulence_y),
1324                blur,
1325                0.8 + c * 0.4,
1326                0.25,
1327            );
1328
1329            // 2. Synaptic Discharge (Gungnir pulses)
1330            if c > 0.2 {
1331                let pulse = (t * (3.0 + c * 5.0)).sin().abs() * c;
1332                let color = [0.0, 0.9, 1.0, 0.4 * pulse]; // Cyan synaptic pulse
1333                renderer.gungnir(rect, color, 12.0 + c * 24.0, 0.6 * pulse);
1334            }
1335
1336            // 3. Unstable Resonance (Magenta/Red shift for high complexity)
1337            if c > 0.7 {
1338                let instability = (t * 15.0).cos().abs() * (c - 0.7) * 3.3;
1339                let warning_color = [1.0, 0.0, 0.4, 0.12 * instability];
1340                renderer.fill_rect(rect, warning_color);
1341                renderer.stroke_rect(rect, [1.0, 0.0, 0.2, 0.45 * instability], 1.8);
1342            }
1343        }
1344        view.render(renderer, rect);
1345    }
1346}
1347
1348/// ObservabilityOverlayModifier bestows omniscient observability over the entire scene graph.
1349#[derive(Debug, Clone, Copy, PartialEq)]
1350pub struct ObservabilityOverlayModifier;
1351
1352impl ViewModifier for ObservabilityOverlayModifier {
1353    fn modify<V: View>(self, content: V) -> impl View {
1354        ModifiedView::new(content, self)
1355    }
1356
1357    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1358        let state = crate::load_system_state();
1359        let t = renderer.elapsed_time();
1360
1361        // 1. Render Background content
1362        view.render(renderer, rect);
1363
1364        if state.realm == UiFidelityLevel::Asgard {
1365            // 2. Bestow Odin's Eye (Atmospheric Overlay)
1366            // Soft, large circular pulse representing the 'Eye'
1367            let eye_pulse = (t * 0.5).sin().abs() * 0.05;
1368            renderer.draw_radial_gradient(
1369                rect,
1370                [0.0, 0.6, 0.8, 0.08 + eye_pulse], // Inner Cyan
1371                [0.0, 0.0, 0.0, 0.0],              // Outer Black
1372            );
1373
1374            // 3. Hugin (Thought) Telemetry - Left Side
1375            let hugin_rect = Rect {
1376                x: rect.x + 20.0,
1377                y: rect.y + 40.0,
1378                width: 200.0,
1379                height: rect.height - 80.0,
1380            };
1381            renderer.draw_text(
1382                "HUGIN: THOUGHT",
1383                hugin_rect.x,
1384                hugin_rect.y,
1385                10.0,
1386                [0.0, 1.0, 1.0, 0.6],
1387            );
1388            for (i, thought) in state.thoughts.iter().rev().take(10).enumerate() {
1389                renderer.draw_text(
1390                    thought,
1391                    hugin_rect.x,
1392                    hugin_rect.y + 20.0 + i as f32 * 14.0,
1393                    9.0,
1394                    [1.0, 1.0, 1.0, 0.4],
1395                );
1396            }
1397
1398            // 4. Munin (Memory) Telemetry - Right Side
1399            let munin_rect = Rect {
1400                x: rect.x + rect.width - 220.0,
1401                y: rect.y + 40.0,
1402                width: 200.0,
1403                height: rect.height - 80.0,
1404            };
1405            renderer.draw_text(
1406                "MUNIN: MEMORY",
1407                munin_rect.x,
1408                munin_rect.y,
1409                10.0,
1410                [1.0, 0.84, 0.0, 0.6],
1411            );
1412            for (i, node) in state.nodes.iter().take(10).enumerate() {
1413                let opacity = (node.weight.min(1.0)) * 0.5;
1414                renderer.draw_text(
1415                    &node.id,
1416                    munin_rect.x,
1417                    munin_rect.y + 20.0 + i as f32 * 14.0,
1418                    9.0,
1419                    [1.0, 1.0, 1.0, opacity],
1420                );
1421            }
1422
1423            // 5. Omniscient Focus Beams (Gungnir Beams)
1424            if let Some(focus_id) = &state.odin_focus {
1425                // Visualize causal links to the focus node
1426                renderer.draw_text(
1427                    &format!("EYE FOCUS: {}", focus_id),
1428                    rect.x + rect.width / 2.0 - 50.0,
1429                    rect.y + 20.0,
1430                    12.0,
1431                    [0.0, 1.0, 1.0, 0.8],
1432                );
1433
1434                // In a real implementation, we would find the rect of the focus_id component.
1435                // For the 'Eye', we manifest a central beam of wisdom.
1436                renderer.gungnir(
1437                    Rect {
1438                        x: rect.x + rect.width / 2.0 - 1.0,
1439                        y: rect.y,
1440                        width: 2.0,
1441                        height: rect.height,
1442                    },
1443                    [0.0, 1.0, 1.0, 1.0],
1444                    20.0,
1445                    0.4,
1446                );
1447            }
1448        }
1449    }
1450}
1451
1452/// Sleipnir spring parameters for the physics solver
1453#[derive(Debug, Clone, Copy, PartialEq)]
1454pub struct SpringParams {
1455    pub stiffness: f32,
1456    pub damping: f32,
1457    pub mass: f32,
1458}
1459
1460impl SpringParams {
1461    pub fn snappy() -> Self {
1462        Self {
1463            stiffness: 230.0,
1464            damping: 22.0,
1465            mass: 1.0,
1466        }
1467    }
1468    pub fn fluid() -> Self {
1469        Self {
1470            stiffness: 170.0,
1471            damping: 26.0,
1472            mass: 1.0,
1473        }
1474    }
1475    pub fn heavy() -> Self {
1476        Self {
1477            stiffness: 90.0,
1478            damping: 20.0,
1479            mass: 1.0,
1480        }
1481    }
1482    pub fn bouncy() -> Self {
1483        Self {
1484            stiffness: 190.0,
1485            damping: 14.0,
1486            mass: 1.0,
1487        }
1488    }
1489}
1490
1491impl Default for SpringParams {
1492    fn default() -> Self {
1493        Self::fluid()
1494    }
1495}
1496
1497#[derive(Debug, Clone, Copy, PartialEq)]
1498struct SolverState {
1499    x: f32,
1500    v: f32,
1501}
1502
1503/// SpringSolver implements a 4th-order Runge-Kutta (RK4) integration for springs.
1504/// This provides superior stability for high-fidelity interactive motion.
1505#[derive(Debug, Clone, Copy, PartialEq)]
1506pub struct SpringSolver {
1507    params: SpringParams,
1508    target: f32,
1509    state: SolverState,
1510}
1511
1512impl SpringSolver {
1513    /// Create a new solver with a target value and starting state.
1514    pub fn new(params: SpringParams, target: f32, current: f32) -> Self {
1515        Self {
1516            params,
1517            target,
1518            state: SolverState { x: current, v: 0.0 },
1519        }
1520    }
1521
1522    /// Advance the simulation by dt seconds using RK4 integration.
1523    pub fn tick(&mut self, dt: f32) -> f32 {
1524        if dt <= 0.0 {
1525            return self.state.x;
1526        }
1527
1528        // Use a fixed time step for stability if dt is too large
1529        let mut remaining = dt;
1530        let step = 1.0 / 120.0;
1531
1532        while remaining > 0.0 {
1533            let d = remaining.min(step);
1534            self.step(d);
1535            remaining -= d;
1536        }
1537
1538        self.state.x
1539    }
1540
1541    fn step(&mut self, dt: f32) {
1542        let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
1543        let b = self.evaluate(self.state, dt * 0.5, a);
1544        let c = self.evaluate(self.state, dt * 0.5, b);
1545        let d = self.evaluate(self.state, dt, c);
1546
1547        let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
1548        let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
1549
1550        self.state.x += dxdt * dt;
1551        self.state.v += dvdt * dt;
1552    }
1553
1554    fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
1555        let state = SolverState {
1556            x: initial.x + d.x * dt,
1557            v: initial.v + d.v * dt,
1558        };
1559        let force =
1560            -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
1561        let mass = self.params.mass.max(0.001);
1562        SolverState {
1563            x: state.v,
1564            v: force / mass,
1565        }
1566    }
1567
1568    pub fn is_settled(&self) -> bool {
1569        (self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
1570    }
1571
1572    pub fn set_target(&mut self, target: f32) {
1573        self.target = target;
1574    }
1575
1576    pub fn current_value(&self) -> f32 {
1577        self.state.x
1578    }
1579}
1580
1581/// SpringAnimationModifier handles physics-based animations via the Sleipnir RK4 solver.
1582#[derive(Debug, Clone, PartialEq)]
1583pub struct SpringAnimationModifier {
1584    pub id: u64,
1585    pub target: f32,
1586    pub params: SpringParams,
1587}
1588
1589impl ViewModifier for SpringAnimationModifier {
1590    fn modify<V: View>(self, content: V) -> impl View {
1591        ModifiedView::new(content, self)
1592    }
1593
1594    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1595        let state = load_system_state();
1596
1597        // Try to fetch the solver from persistent state.
1598        let solver_lock_opt = state.get_component_state::<SpringSolver>(self.id);
1599
1600        let current_val;
1601
1602        if let Some(lock) = solver_lock_opt {
1603            // Found a solver. Tick it.
1604            let mut solver = lock.write().unwrap();
1605            solver.set_target(self.target);
1606            current_val = solver.tick(renderer.delta_time());
1607
1608            // If the solver hasn't settled yet, request another frame.
1609            if !solver.is_settled() {
1610                renderer.request_redraw();
1611            }
1612        } else {
1613            // First time seeing this ID. Initialize solver state.
1614            let solver = SpringSolver::new(
1615                self.params,
1616                self.target,
1617                self.target, // Initialize at target to avoid jump on first frame
1618            );
1619
1620            // Insert into registry for next frame.
1621            get_system_state().rcu(|old| {
1622                let mut new_state = (**old).clone();
1623                new_state.set_component_state(self.id, solver);
1624                new_state
1625            });
1626
1627            current_val = self.target;
1628        }
1629
1630        // Apply the solved value as a vertical translation.
1631        renderer.push_transform([0.0, current_val], [1.0, 1.0], 0.0);
1632        view.render(renderer, rect);
1633        renderer.pop_transform();
1634    }
1635}
1636
1637/// TransformModifier applies a 2D transform (translation, scale, rotation) to its child.
1638/// This modifier is "layout-neutral" and can be animated without re-running the layout engine.
1639#[derive(Debug, Clone, Copy, PartialEq)]
1640pub struct TransformModifier {
1641    pub translation: [f32; 2],
1642    pub scale: [f32; 2],
1643    pub rotation: f32,
1644}
1645
1646impl Default for TransformModifier {
1647    fn default() -> Self {
1648        Self::new()
1649    }
1650}
1651
1652impl TransformModifier {
1653    pub fn new() -> Self {
1654        Self {
1655            translation: [0.0, 0.0],
1656            scale: [1.0, 1.0],
1657            rotation: 0.0,
1658        }
1659    }
1660
1661    pub fn translate(mut self, x: f32, y: f32) -> Self {
1662        self.translation = [x, y];
1663        self
1664    }
1665
1666    pub fn scale(mut self, x: f32, y: f32) -> Self {
1667        self.scale = [x, y];
1668        self
1669    }
1670
1671    pub fn rotate(mut self, radians: f32) -> Self {
1672        self.rotation = radians;
1673        self
1674    }
1675}
1676
1677impl ViewModifier for TransformModifier {
1678    fn modify<V: View>(self, content: V) -> impl View {
1679        ModifiedView::new(content, self)
1680    }
1681
1682    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
1683        renderer.push_transform(self.translation, self.scale, self.rotation);
1684        view.render(renderer, rect);
1685        renderer.pop_transform();
1686    }
1687}
1688
1689/// LifecycleModifier handles on_appear and on_disappear hooks.
1690
1691#[derive(Clone)]
1692pub struct LifecycleModifier {
1693    pub on_appear: Option<Arc<dyn Fn() + Send + Sync>>,
1694    pub on_disappear: Option<Arc<dyn Fn() + Send + Sync>>,
1695}
1696
1697impl ViewModifier for LifecycleModifier {
1698    fn modify<V: View>(self, content: V) -> impl View {
1699        ModifiedView::new(content, self)
1700    }
1701}
1702
1703/// OpacityModifier fades this view and all its descendants to the given alpha.
1704/// The renderer is expected to honour `push_opacity`/`pop_opacity` on the Renderer trait.
1705#[derive(Debug, Clone, Copy, PartialEq)]
1706pub struct OpacityModifier {
1707    pub opacity: f32,
1708}
1709
1710impl ViewModifier for OpacityModifier {
1711    fn modify<V: View>(self, content: V) -> impl View {
1712        ModifiedView::new(content, self)
1713    }
1714
1715    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1716        renderer.push_opacity(self.opacity);
1717    }
1718
1719    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1720        renderer.pop_opacity();
1721    }
1722}
1723
1724/// OnClickModifier registers a click handler for this view.
1725#[derive(Clone)]
1726pub struct OnClickModifier {
1727    pub action: Arc<dyn Fn() + Send + Sync>,
1728}
1729
1730impl ViewModifier for OnClickModifier {
1731    fn modify<V: View>(self, content: V) -> impl View {
1732        ModifiedView::new(content, self)
1733    }
1734
1735    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1736        let action = self.action.clone();
1737        renderer.register_handler(
1738            "pointerclick",
1739            std::sync::Arc::new(move |event| {
1740                if let Event::PointerClick { .. } = event {
1741                    (action)();
1742                }
1743            }),
1744        );
1745    }
1746}
1747
1748/// OnPointerEnterModifier registers a pointer enter handler.
1749#[derive(Clone)]
1750pub struct OnPointerEnterModifier {
1751    pub action: Arc<dyn Fn() + Send + Sync>,
1752}
1753
1754impl ViewModifier for OnPointerEnterModifier {
1755    fn modify<V: View>(self, content: V) -> impl View {
1756        ModifiedView::new(content, self)
1757    }
1758
1759    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1760        let action = self.action.clone();
1761        renderer.register_handler(
1762            "pointerenter",
1763            std::sync::Arc::new(move |event| {
1764                if let Event::PointerEnter = event {
1765                    (action)();
1766                }
1767            }),
1768        );
1769    }
1770}
1771
1772/// OnPointerLeaveModifier registers a pointer leave handler.
1773#[derive(Clone)]
1774pub struct OnPointerLeaveModifier {
1775    pub action: Arc<dyn Fn() + Send + Sync>,
1776}
1777
1778impl ViewModifier for OnPointerLeaveModifier {
1779    fn modify<V: View>(self, content: V) -> impl View {
1780        ModifiedView::new(content, self)
1781    }
1782
1783    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1784        let action = self.action.clone();
1785        renderer.register_handler(
1786            "pointerleave",
1787            std::sync::Arc::new(move |event| {
1788                if let Event::PointerLeave = event {
1789                    (action)();
1790                }
1791            }),
1792        );
1793    }
1794}
1795
1796/// OnPointerMoveModifier registers a pointer move handler.
1797#[derive(Clone)]
1798pub struct OnPointerMoveModifier {
1799    pub action: Arc<dyn Fn(f32, f32) + Send + Sync>,
1800}
1801
1802impl ViewModifier for OnPointerMoveModifier {
1803    fn modify<V: View>(self, content: V) -> impl View {
1804        ModifiedView::new(content, self)
1805    }
1806
1807    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1808        let action = self.action.clone();
1809        renderer.register_handler(
1810            "pointermove",
1811            std::sync::Arc::new(move |event| {
1812                if let Event::PointerMove { x, y, .. } = event {
1813                    (action)(x, y);
1814                }
1815            }),
1816        );
1817    }
1818}
1819
1820/// OnPointerDownModifier registers a pointer down handler.
1821#[derive(Clone)]
1822pub struct OnPointerDownModifier {
1823    pub action: Arc<dyn Fn() + Send + Sync>,
1824}
1825
1826impl ViewModifier for OnPointerDownModifier {
1827    fn modify<V: View>(self, content: V) -> impl View {
1828        ModifiedView::new(content, self)
1829    }
1830
1831    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1832        let action = self.action.clone();
1833        renderer.register_handler(
1834            "pointerdown",
1835            std::sync::Arc::new(move |event| {
1836                if let Event::PointerDown { .. } = event {
1837                    (action)();
1838                }
1839            }),
1840        );
1841    }
1842}
1843
1844/// OnPointerUpModifier registers a pointer up handler.
1845#[derive(Clone)]
1846pub struct OnPointerUpModifier {
1847    pub action: Arc<dyn Fn() + Send + Sync>,
1848}
1849
1850impl ViewModifier for OnPointerUpModifier {
1851    fn modify<V: View>(self, content: V) -> impl View {
1852        ModifiedView::new(content, self)
1853    }
1854
1855    fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1856        let action = self.action.clone();
1857        renderer.register_handler(
1858            "pointerup",
1859            std::sync::Arc::new(move |event| {
1860                if let Event::PointerUp { .. } = event {
1861                    (action)();
1862                }
1863            }),
1864        );
1865    }
1866}
1867
1868/// ForegroundColorModifier overrides the foreground (text / icon) color inherited
1869/// by all descendants until another ForegroundColorModifier is encountered.
1870#[derive(Debug, Clone, Copy, PartialEq)]
1871pub struct ForegroundColorModifier {
1872    pub color: [f32; 4],
1873}
1874
1875impl ViewModifier for ForegroundColorModifier {
1876    fn modify<V: View>(self, content: V) -> impl View {
1877        ModifiedView::new(content, self)
1878    }
1879}
1880
1881/// ClipModifier restricts all child drawing to the view's layout rectangle.
1882/// The renderer must support `push_clip_rect`/`pop_clip_rect`.
1883#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1884pub struct ClipModifier;
1885
1886impl ViewModifier for ClipModifier {
1887    fn modify<V: View>(self, content: V) -> impl View {
1888        ModifiedView::new(content, self)
1889    }
1890
1891    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1892        renderer.push_clip_rect(rect);
1893    }
1894
1895    fn post_render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
1896        renderer.pop_clip_rect();
1897    }
1898}
1899
1900/// BorderModifier draws a solid-color border around the view bounds.
1901#[derive(Debug, Clone, Copy, PartialEq)]
1902pub struct BorderModifier {
1903    pub color: [f32; 4],
1904    pub width: f32,
1905}
1906
1907impl ViewModifier for BorderModifier {
1908    fn modify<V: View>(self, content: V) -> impl View {
1909        ModifiedView::new(content, self)
1910    }
1911
1912    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1913        renderer.stroke_rect(rect, self.color, self.width);
1914    }
1915}
1916
1917// Primitive (leaf) views implement Never as body
1918#[doc(hidden)]
1919pub enum Never {}
1920
1921impl View for Never {
1922    type Body = Never;
1923    fn body(self) -> Never {
1924        // SAFETY: `Never` is an uninhabitable enum (zero variants), so this function
1925        // can never be called with a valid value and can never return.
1926        unreachable!()
1927    }
1928}
1929
1930/// EmptyView - A view that renders nothing and takes up no space.
1931#[derive(Debug, Clone, Copy, Default)]
1932pub struct EmptyView;
1933
1934impl View for EmptyView {
1935    type Body = Never;
1936    fn body(self) -> Self::Body {
1937        // SAFETY: `Never` is uninhabitable. EmptyView renders nothing and has no
1938        // composable body -- it registers zero paint commands.
1939        unreachable!()
1940    }
1941    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
1942    fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
1943        Size {
1944            width: 0.0,
1945            height: 0.0,
1946        }
1947    }
1948}
1949
1950/// A view that has been transformed by a modifier.
1951/// Section 4.3: "Each modifier implements ViewModifier and produces a ModifiedView<Inner, Self>."
1952#[derive(Clone)]
1953pub struct ModifiedView<V, M> {
1954    view: V,
1955    modifier: M,
1956}
1957
1958impl<V: View, M: ViewModifier> ModifiedView<V, M> {
1959    #[doc(hidden)]
1960    pub fn new(view: V, modifier: M) -> Self {
1961        Self { view, modifier }
1962    }
1963}
1964
1965impl<V: View, M: ViewModifier> View for ModifiedView<V, M> {
1966    type Body = ModifiedView<V::Body, M>;
1967
1968    fn body(self) -> Self::Body {
1969        ModifiedView {
1970            view: self.view.body(),
1971            modifier: self.modifier.clone(),
1972        }
1973    }
1974
1975    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
1976        self.modifier.render_view(&self.view, renderer, rect);
1977    }
1978
1979    fn intrinsic_size(&self, renderer: &mut dyn Renderer, proposal: SizeProposal) -> Size {
1980        self.modifier.measure_view(&self.view, renderer, proposal)
1981    }
1982
1983    fn flex_weight(&self) -> f32 {
1984        self.modifier.child_flex_weight(&self.view)
1985    }
1986
1987    fn layout(&self) -> Option<&dyn layout::LayoutView> {
1988        self.modifier.layout().or_else(|| self.view.layout())
1989    }
1990
1991    fn get_grid_placement(&self) -> Option<GridPlacement> {
1992        self.modifier
1993            .get_grid_placement()
1994            .or_else(|| self.view.get_grid_placement())
1995    }
1996}
1997
1998pub trait ViewModifier: Send + Clone {
1999    fn modify<V: View>(self, content: V) -> impl View;
2000
2001    /// Returns the grid placement configuration if this modifier defines one.
2002    fn get_grid_placement(&self) -> Option<GridPlacement> {
2003        None
2004    }
2005
2006    /// Core rendering hook called before child views.
2007    fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2008
2009    /// Cleanup hook called after child views.
2010    fn post_render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {}
2011
2012    /// Allows a modifier to completely override or wrap the rendering of its child.
2013    /// Default implementation performs a standard push -> transform -> render child -> pop sequence.
2014    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
2015        self.render(renderer, rect);
2016        let child_rect = self.transform_rect(rect);
2017        view.render(renderer, child_rect);
2018        self.post_render(renderer, rect);
2019    }
2020
2021    fn transform_rect(&self, rect: Rect) -> Rect {
2022        rect
2023    }
2024
2025    /// Allows a modifier to transform the layout proposal before it reaches the child.
2026    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
2027        proposal
2028    }
2029
2030    /// Allows a modifier to transform the resulting size from the child.
2031    fn transform_size(&self, size: Size) -> Size {
2032        size
2033    }
2034
2035    /// Measure hook that coordinates size propagation.
2036    fn measure_view<V: View>(
2037        &self,
2038        view: &V,
2039        renderer: &mut dyn Renderer,
2040        proposal: SizeProposal,
2041    ) -> Size {
2042        let child_proposal = self.transform_proposal(proposal);
2043        let child_size = view.intrinsic_size(renderer, child_proposal);
2044        self.transform_size(child_size)
2045    }
2046
2047    /// Allows a modifier to override or pass through the child's flex weight.
2048    fn child_flex_weight<V: View>(&self, view: &V) -> f32 {
2049        view.flex_weight()
2050    }
2051
2052    fn layout(&self) -> Option<&dyn layout::LayoutView> {
2053        None
2054    }
2055}
2056
2057/// Captures the depth of every stack-pushing operation on the `Renderer`.
2058///
2059/// Created via `Renderer::snapshot_render_state()` and consumed by
2060/// `Renderer::restore_render_state()`. The renderer uses this to recover
2061/// from mid-render panics -- any items pushed beyond the snapshot point
2062/// are popped so sibling views drawn afterward don't inherit leaked
2063/// clip / opacity / transform / shadow / vnode / mjolnir-slice state.
2064///
2065/// Frame-scoped: the renderer resets all stacks in `begin_frame()` so a
2066/// snapshot taken in one frame is meaningless in another.
2067#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
2068pub struct RenderStateSnapshot {
2069    pub clip_depth: u32,
2070    pub opacity_depth: u32,
2071    pub slice_depth: u32,
2072    pub shadow_depth: u32,
2073    pub transform_depth: u32,
2074    pub vnode_depth: u32,
2075}
2076
2077/// TelemetryData tracks real-time performance metrics for the GPU renderer.
2078#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
2079pub struct TelemetryData {
2080    pub frame_time_ms: f32,
2081    /// Total frame budget in milliseconds for the active policy.
2082    pub frame_budget_ms: f32,
2083    /// Remaining frame budget after the frame completed; negative means over budget.
2084    pub frame_budget_remaining_ms: f32,
2085    /// Remaining layout budget after layout completed; negative means the layout slice was exceeded.
2086    pub layout_budget_remaining_ms: f32,
2087    /// Whether the frame exceeded the total budget.
2088    pub frame_over_budget: bool,
2089    /// Whether the layout phase exceeded its budget slice.
2090    pub layout_over_budget: bool,
2091    /// 99th percentile frame time over the last window, used to detect tail latency.
2092    pub p99_frame_time_ms: f32,
2093    /// Statistical jitter (variance in frame timing).
2094    pub frame_jitter_ms: f32,
2095    /// Indicates if a hardware stall (DRAM refresh, thermal spike) was detected.
2096    pub hardware_stall_detected: bool,
2097
2098    // Pass timing
2099    pub input_time_ms: f32,
2100    pub state_flush_time_ms: f32,
2101    pub layout_time_ms: f32,
2102    pub draw_time_ms: f32,
2103    pub gpu_submit_time_ms: f32,
2104
2105    pub draw_calls: u32,
2106    pub vertices: u32,
2107
2108    /// Global Berserker Pipeline Intensity (0.0 - 1.0+)
2109    pub berserker_rage: f32,
2110
2111    // Memory breakdown
2112    pub vram_usage_mb: f32,
2113    pub vram_textures_mb: f32,
2114    pub vram_buffers_mb: f32,
2115    pub vram_pipelines_mb: f32,
2116    /// Indicates if the Mega-Atlas or VRAM pools are at capacity.
2117    pub vram_exhausted: bool,
2118}
2119
2120/// Configuration for render-loop frame timing and degradation strategies.
2121#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
2122pub struct FrameBudget {
2123    /// Target frame time in milliseconds (default: 16.0 for 60FPS)
2124    pub target_ms: f32,
2125    /// If true, the renderer is allowed to dynamically skip non-critical effects
2126    /// (like heavy blurs or complex shadows) when the budget is exceeded.
2127    pub allow_degradation: bool,
2128}
2129
2130impl Default for FrameBudget {
2131    fn default() -> Self {
2132        Self {
2133            target_ms: 16.0,
2134            allow_degradation: true,
2135        }
2136    }
2137}
2138
2139/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
2140/// This trait is object-safe and used by the View::render system.
2141/// # Implementation Requirements
2142/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
2143/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
2144/// 3. All operations must be batchable by the underlying backend.
2145///    Trait providing timing information for the render loop.
2146pub trait ElapsedTime {
2147    /// Returns the cumulative time since the renderer started in seconds.
2148    fn elapsed_time(&self) -> f32;
2149
2150    /// Returns the time elapsed since the last frame in seconds.
2151    fn delta_time(&self) -> f32;
2152}
2153
2154/// The Renderer trait defines the atomic drawing operations for all CVKG backends.
2155/// This trait is object-safe and used by the View::render system.
2156/// # Implementation Requirements
2157/// 1. Coordinate system is origin-top-left (0,0) with Y increasing downwards.
2158/// 2. Colors are [R, G, B, A] in the [0.0, 1.0] range.
2159/// 3. All operations must be batchable by the underlying backend.
2160///
2161/// Sub-traits in `renderer/mod.rs` (RendererCore, RendererShapes, etc.) provide
2162/// logical groupings for consumer code. Backends implement this monolithic trait;
2163/// the sub-traits are aspirational documentation and NOT enforced as supertraits
2164/// to avoid method ambiguity (sub-traits re-declare the same methods).
2165pub trait Renderer: ElapsedTime + Send {
2166    /// Requests that the renderer redraws as soon as possible.
2167    /// Used for continuous animations.
2168    fn request_redraw(&mut self) {}
2169
2170    /// Returns true if the current frame is over the time budget.
2171    /// This can be used to skip expensive visual effects.
2172    fn is_over_budget(&self) -> bool {
2173        false
2174    }
2175
2176    // ── Filled shapes ────────────────────────────────────────────────────
2177    fn fill_rect(&mut self, rect: Rect, color: [f32; 4]);
2178    fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]);
2179    /// Fill an ellipse/circle that fits inside `rect`.
2180    fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]);
2181
2182    /// Draw a background image that fills the entire rect.
2183    /// This is a convenience wrapper around `draw_image` for the common case
2184    /// of a full-rect background. The image must have been pre-warmed via
2185    /// `prewarm_vram` before the first frame.
2186    fn draw_background_image(&mut self, image_name: &str, rect: Rect) {
2187        Renderer::draw_image(self, image_name, rect);
2188    }
2189
2190    /// Fill a rounded rect with glass material for frosted backdrop effect.
2191    /// This is the proper way to render glass cards for macOS Tahoe-style blur.
2192    /// The blur_radius controls the intensity of the backdrop blur.
2193    fn fill_glass_rect(&mut self, rect: Rect, radius: f32, blur_radius: f32) {
2194        // Default no-op implementation; GPU backend overrides
2195        let _ = (rect, radius, blur_radius);
2196    }
2197    /// Fill a rounded rect with glass material with explicit intensity control.
2198    /// `glass_intensity` ranges from 0.0 (solid) to 1.0 (full glass). Default: 1.0.
2199    fn fill_glass_rect_with_intensity(&mut self, rect: Rect, radius: f32, blur_radius: f32, glass_intensity: f32) {
2200        let _ = (rect, radius, blur_radius, glass_intensity);
2201    }
2202    /// Fill a rounded rect with glass material with explicit tint color and intensity.
2203    /// `tint_color` is the glass fill color (RGBA). `glass_intensity` ranges from 0.0 (solid) to 1.0 (full glass).
2204    fn fill_glass_rect_with_tint(&mut self, rect: Rect, radius: f32, blur_radius: f32, tint_color: [f32; 4], glass_intensity: f32) {
2205        // Default: delegate to intensity-only version using tint color as a simple fill
2206        let _ = (rect, radius, blur_radius, tint_color, glass_intensity);
2207    }
2208    /// Fill a rounded rect with glass material, modulated by touch pressure.
2209    /// `pressure` ranges from 0.0 (no touch) to 1.0 (full pressure).
2210    /// When pressure > 0, refraction distortion is scaled by pressure amount.
2211    /// Desktop stub: pressure is always 1.0 for mouse clicks, 0.0 otherwise.
2212    fn fill_glass_rect_with_pressure(&mut self, rect: Rect, radius: f32, blur_radius: f32, pressure: f32) {
2213        // Default: delegate to standard glass with intensity = pressure
2214        Renderer::fill_glass_rect_with_intensity(self, rect, radius, blur_radius, pressure);
2215    }
2216
2217    /// Fill a squircle (superellipse) for Apple-style icon silhouettes.
2218    /// `n` controls the squareness: 2.0 = rounded rect, 4.0 = classic squircle, higher = more square.
2219    fn fill_squircle(&mut self, rect: Rect, _n: f32, color: [f32; 4]) {
2220        // Default fallback to rounded rect
2221        Renderer::fill_rounded_rect(self, rect, rect.width.min(rect.height) * 0.22, color);
2222    }
2223
2224    /// Stroke a squircle (superellipse) outline.
2225    fn stroke_squircle(&mut self, rect: Rect, _n: f32, color: [f32; 4], stroke_width: f32) {
2226        Renderer::stroke_rounded_rect(self, rect, rect.width.min(rect.height) * 0.22, color, stroke_width);
2227    }
2228
2229    /// Draw a focus ring around a rect (for keyboard navigation accessibility).
2230    /// `offset` is the gap between the rect and the ring, `width` is the ring thickness.
2231    fn draw_focus_ring(&mut self, rect: Rect, radius: f32, offset: f32, width: f32, color: [f32; 4]) {
2232        // Default fallback to a stroked rounded rect
2233        let ring_rect = Rect {
2234            x: rect.x - offset,
2235            y: rect.y - offset,
2236            width: rect.width + 2.0 * offset,
2237            height: rect.height + 2.0 * offset,
2238        };
2239        Renderer::stroke_rounded_rect(self, ring_rect, radius + offset, color, width);
2240    }
2241
2242    /// Draw a high-fidelity 3D cube inside the given rectangle using specialized shader logic.
2243    /// `rotation` is [pitch, yaw, roll] in radians.
2244    fn draw_3d_cube(&mut self, _rect: Rect, _color: [f32; 4], _rotation: [f32; 3]) {}
2245
2246    // ── Stroked shapes ───────────────────────────────────────────────────
2247    fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2248    fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32);
2249    /// Stroke an ellipse/circle that fits inside `rect`.
2250    fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32);
2251    /// Draw a straight line from (x1,y1) to (x2,y2).
2252    fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32);
2253    /// Fill a polygon defined by a set of vertices.
2254    fn fill_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4]) {}
2255    /// Stroke a polygon defined by a set of vertices.
2256    fn stroke_polygon(&mut self, _vertices: &[[f32; 2]], _color: [f32; 4], _stroke_width: f32) {}
2257
2258    // ── Text ─────────────────────────────────────────────────────────────
2259    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
2260        let span = cvkg_runic_text::TextSpan::new(
2261            text,
2262            cvkg_runic_text::TextStyle {
2263                family: "Inter".to_string(),
2264                font_size: size,
2265                color: [(color[0]*255.0) as u8, (color[1]*255.0) as u8, (color[2]*255.0) as u8, (color[3]*255.0) as u8],
2266                fallback_families: vec![
2267                    "SF Pro".to_string(),
2268                    "SF Pro Text".to_string(),
2269                    "Helvetica Neue".to_string(),
2270                    "Helvetica".to_string(),
2271                    "Arial".to_string(),
2272                    "sans-serif".to_string(),
2273                ],
2274                ..Default::default()
2275            },
2276        );
2277        if let Some(shaped) = Renderer::shape_rich_text(
2278            self,
2279            &[span],
2280            None,
2281            cvkg_runic_text::TextAlign::Start,
2282            cvkg_runic_text::TextOverflow::Visible,
2283        ) {
2284            Renderer::draw_shaped_text(self, &shaped, x, y);
2285        }
2286    }
2287
2288    /// Measure the width and height of the specified text.
2289    fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
2290        let span = cvkg_runic_text::TextSpan::new(
2291            text,
2292            cvkg_runic_text::TextStyle {
2293                family: "Inter".to_string(),
2294                font_size: size,
2295                fallback_families: vec![
2296                    "SF Pro".to_string(),
2297                    "SF Pro Text".to_string(),
2298                    "Helvetica Neue".to_string(),
2299                    "Helvetica".to_string(),
2300                    "Arial".to_string(),
2301                    "sans-serif".to_string(),
2302                ],
2303                ..Default::default()
2304            },
2305        );
2306        if let Some(shaped) = Renderer::shape_rich_text(
2307            self,
2308            &[span],
2309            None,
2310            cvkg_runic_text::TextAlign::Start,
2311            cvkg_runic_text::TextOverflow::Visible,
2312        ) {
2313            let scale = self.text_scale_factor().max(1.0);
2314            (shaped.width / scale, shaped.height / scale)
2315        } else {
2316            (0.0, 0.0)
2317        }
2318    }
2319
2320    /// Return the baseline offset (ascent) for the given text and size.
2321    /// This is the distance from the text origin (y in draw_text) to the baseline.
2322    /// Default returns 0.0; override in renderers that support text shaping.
2323    fn measure_text_baseline(&mut self, text: &str, size: f32) -> f32 {
2324        let span = cvkg_runic_text::TextSpan::new(
2325            text,
2326            cvkg_runic_text::TextStyle {
2327                family: "Inter".to_string(),
2328                font_size: size,
2329                fallback_families: vec![
2330                    "SF Pro".to_string(),
2331                    "SF Pro Text".to_string(),
2332                    "Helvetica Neue".to_string(),
2333                    "Helvetica".to_string(),
2334                    "Arial".to_string(),
2335                    "sans-serif".to_string(),
2336                ],
2337                ..Default::default()
2338            },
2339        );
2340        if let Some(shaped) = Renderer::shape_rich_text(
2341            self,
2342            &[span],
2343            None,
2344            cvkg_runic_text::TextAlign::Start,
2345            cvkg_runic_text::TextOverflow::Visible,
2346        ) {
2347            shaped.ascent / self.text_scale_factor().max(1.0)
2348        } else {
2349            0.0
2350        }
2351    }
2352
2353    /// Scale factor used by text measurement helpers.
2354    ///
2355    /// Renderers that shape text in device pixels should return their current
2356    /// device scale so `measure_text` and `measure_text_baseline` stay in logical pixels.
2357    fn text_scale_factor(&self) -> f32 {
2358        1.0
2359    }
2360
2361    fn shape_rich_text(
2362        &mut self,
2363        _spans: &[cvkg_runic_text::TextSpan],
2364        _max_width: Option<f32>,
2365        _align: cvkg_runic_text::TextAlign,
2366        _overflow: cvkg_runic_text::TextOverflow,
2367    ) -> Option<cvkg_runic_text::ShapedText> {
2368        None
2369    }
2370
2371    fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {}
2372
2373    // ── Images & textures ────────────────────────────────────────────────
2374    /// Draw a texture (GPU-side) at the specified rect.
2375    fn draw_texture(&mut self, _texture_id: u32, _rect: Rect) {}
2376    /// Draw an image asset by name or path.
2377    fn draw_image(&mut self, _image_name: &str, _rect: Rect) {}
2378    /// Load an image asset from memory.
2379    fn load_image(&mut self, _name: &str, _data: &[u8]) {}
2380    /// Pre-warm the renderer with assets. Implementations can use this
2381    /// to populate texture atlases or warm up shader caches.
2382    fn prewarm_vram(&mut self, _assets: Vec<(String, Vec<u8>)>) {}
2383
2384    /// Get the current pointer (mouse/touch) position.
2385    fn get_pointer_position(&self) -> [f32; 2] {
2386        [0.0, 0.0]
2387    }
2388
2389    // ── Data Visualization ───────────────────────────────────────────────
2390    /// Upload raw float data as a GPU texture for heatmap rendering.
2391    fn upload_data_texture(&mut self, _id: &str, _data: &[f32], _width: u32, _height: u32) {}
2392    /// Draw a heatmap using a previously uploaded data texture.
2393    fn draw_heatmap(&mut self, _texture_id: &str, _rect: Rect, _palette: &str) {}
2394
2395    // ── 3D Objects ───────────────────────────────────────────────────────
2396    /// Draw a 3D mesh.
2397    fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {}
2398
2399    /// Draw a 3D mesh with full material and transform support.
2400    fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
2401
2402    /// Set the 3D camera for perspective/orthographic projection.
2403    /// If not called, rendering defaults to the 2D orthographic projection.
2404    fn set_camera_3d(&mut self, _camera: &Camera3D) {}
2405
2406    /// Push a 3D transform onto the transform stack.
2407    /// All subsequent drawing is affected until `pop_transform_3d`.
2408    fn push_transform_3d(&mut self, _transform: &Transform3D) {}
2409
2410    /// Pop the most recently pushed 3D transform.
2411    fn pop_transform_3d(&mut self) {}
2412
2413    /// Render a 3D scene graph node. Reads position_3d, rotation_3d, scale_3d
2414    /// from the node and emits the appropriate draw call.
2415    /// Default implementation is a no-op; 3D renderers override this.
2416    ///
2417    /// `position`: [x, y, z] world-space position
2418    /// `rotation`: [x, y, z, w] quaternion rotation
2419    /// `scale`: [x, y, z] scale factors
2420    /// `color`: [r, g, b, a] base color for unlit rendering
2421    fn render_scene_node_3d(
2422        &mut self,
2423        _position: [f32; 3],
2424        _rotation: [f32; 4],
2425        _scale: [f32; 3],
2426        _color: [f32; 4],
2427        _meshes: &[Mesh],
2428    ) {
2429        // Default no-op: 2D renderers ignore 3D scene nodes
2430    }
2431
2432    /// Draw a linear gradient between two colors at the specified angle.
2433    fn draw_linear_gradient(
2434        &mut self,
2435        _rect: Rect,
2436        _start_color: [f32; 4],
2437        _end_color: [f32; 4],
2438        _angle: f32,
2439    ) {
2440    }
2441    /// Draw a radial gradient between two colors.
2442    fn draw_radial_gradient(
2443        &mut self,
2444        _rect: Rect,
2445        _inner_color: [f32; 4],
2446        _outer_color: [f32; 4],
2447    ) {
2448    }
2449    /// Draw a high-fidelity drop shadow for a rounded rectangle.
2450    fn draw_drop_shadow(
2451        &mut self,
2452        _rect: Rect,
2453        _radius: f32,
2454        _color: [f32; 4],
2455        _blur: f32,
2456        _spread: f32,
2457    ) {
2458    }
2459    /// Draw a dashed border for a rounded rectangle.
2460    fn stroke_dashed_rounded_rect(
2461        &mut self,
2462        _rect: Rect,
2463        _radius: f32,
2464        _color: [f32; 4],
2465        _width: f32,
2466        _dash: f32,
2467        _gap: f32,
2468    ) {
2469    }
2470    /// Draw a 9-slice / patch scaled image.
2471    fn draw_9slice(
2472        &mut self,
2473        _image_name: &str,
2474        _rect: Rect,
2475        _left: f32,
2476        _top: f32,
2477        _right: f32,
2478        _bottom: f32,
2479    ) {
2480    }
2481
2482    // ── Clipping ─────────────────────────────────────────────────────────
2483    /// Push a clip rectangle.  All subsequent drawing is clipped to `rect`.
2484    /// Implementations that do not support clipping may ignore this call.
2485    fn push_clip_rect(&mut self, _rect: Rect) {}
2486    /// Pop the most recently pushed clip rectangle.
2487    fn pop_clip_rect(&mut self) {}
2488    /// Get the current clip rectangle in screen coordinates.
2489    /// Returns a rect covering the entire screen if no clip is active.
2490    fn current_clip_rect(&self) -> Rect {
2491        Rect::new(-10000.0, -10000.0, 20000.0, 20000.0)
2492    }
2493
2494    // ── Global opacity ───────────────────────────────────────────────────
2495    /// Set a global opacity multiplier applied to all subsequent draw calls
2496    /// until `pop_opacity` is called.  `opacity` is in [0.0, 1.0].
2497    fn push_opacity(&mut self, _opacity: f32) {}
2498    /// Restore the previous opacity level.
2499    fn pop_opacity(&mut self) {}
2500
2501    // ── Berserker Pipeline State ─────────────────────────────────────────
2502    fn set_theme(&mut self, _theme: ColorTheme) {}
2503    fn set_rage(&mut self, _rage: f32) {}
2504    fn set_berserker_mode(&mut self, _state: RenderIntensityMode) {}
2505    fn trigger_shatter_event(&mut self, _origin: [f32; 2], _force: f32) {}
2506    /// Set the fireball position for dynamic glass specular highlights.
2507    fn set_fireball_pos(&mut self, _pos: [f32; 2]) {}
2508    /// Set the desktop scene preset (Aurora, Void, Nebula, Glitch, Yggdrasil).
2509    fn set_scene(&mut self, _scene: &str) {}
2510    /// Set the desktop scene by name. Case-insensitive.
2511    /// Supports: "aurora", "void", "nebula", "glitch", "yggdrasil".
2512    /// Aliases: "empty", "none", "blank" → Void.
2513    fn set_scene_by_name(&mut self, name: &str) {
2514        if let Some(preset) = resolve_scene_by_name(name) {
2515            Renderer::set_scene_preset(self, preset);
2516        }
2517    }
2518
2519    // ── Export & Print ───────────────────────────────────────────────────
2520    /// Capture the current frame as a PNG byte buffer.
2521    fn capture_png(&mut self) -> Vec<u8> {
2522        Vec::new()
2523    }
2524    /// Trigger a native print dialog or spooling operation.
2525    fn print(&mut self) {}
2526
2527    fn set_scene_preset(&mut self, _preset: u32) {}
2528
2529    // ── Cyberpunk Effects ────────────────────────────────────────────────
2530    /// Apply a Bifrost (Frosted Glass) effect to the specified rect.
2531    fn bifrost(&mut self, _rect: Rect, _blur: f32, _saturation: f32, _opacity: f32) {}
2532    /// Apply a Gungnir (Neon Glow) effect to the specified rect.
2533    fn gungnir(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2534    /// Soft glow variant -- half the intensity of gungnir(). Use for hover highlights.
2535    fn gungnir_soft(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32, _intensity: f32) {}
2536    /// Set the default background color for the canvas (RGBA).
2537    /// Used when the app does not draw its own background.
2538    fn set_default_background_color(&mut self, _color: [f32; 4]) {}
2539    /// Apply a ManiGlow (Lunar Illuminator) effect.
2540    fn mani_glow(&mut self, _rect: Rect, _color: [f32; 4], _radius: f32) {}
2541    /// Push a Mjolnir Slice (geometric clipping).
2542    fn push_mjolnir_slice(&mut self, _angle: f32, _offset: f32) {}
2543    fn pop_mjolnir_slice(&mut self) {}
2544    /// Execute a render function with memoization.
2545    /// If the renderer supports caching and the `id` + `data_hash` match a previous run,
2546    /// it may replay cached commands instead of executing the function.
2547    fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer));
2548    /// Capture current renderer stack depths for later panic recovery.
2549    /// The default implementation returns `RenderStateSnapshot::default()`,
2550    /// which is safe but does nothing useful -- backends with stack state
2551    /// must override this to record their actual depths.
2552    fn snapshot_render_state(&self) -> RenderStateSnapshot {
2553        RenderStateSnapshot::default()
2554    }
2555    /// Restore renderer stack state by popping items pushed beyond the
2556    /// snapshot point. Used by `ErrorBoundary` to recover from mid-render
2557    /// panics so sibling views don't inherit leaked clip/opacity/transform
2558    /// state. Idempotent: a no-op if stacks are already at or below the
2559    /// snapshot depths. Default implementation is a no-op for backends
2560    /// that have no stack state.
2561    fn restore_render_state(&mut self, _snap: RenderStateSnapshot) {}
2562    /// Apply a Mjolnir Shatter effect (fragmentation) to the specified rect.
2563    fn mjolnir_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2564    fn mjolnir_fluid_shatter(&mut self, _rect: Rect, _pieces: u32, _force: f32, _color: [f32; 4]) {}
2565    fn draw_mjolnir_bolt(&mut self, _from: [f32; 2], _to: [f32; 2], _color: [f32; 4]) {}
2566
2567    // ── Futuristic UI Compute & Volumetric ───────────────────────────────
2568    /// Dispatches a burst of GPU particles (e.g. fireworks, data streams).
2569    fn dispatch_particles(
2570        &mut self,
2571        _origin: [f32; 2],
2572        _count: u32,
2573        _effect_type: &str,
2574        _color: [f32; 4],
2575    ) {
2576    }
2577
2578    /// Draws a volumetric hologram into the specified bounding rectangle.
2579    fn draw_hologram(&mut self, _rect: Rect, _hologram_id: &str, _time: f32) {}
2580
2581    // ── Accessibility (ShieldWall) ───────────────────────────────────────
2582    fn set_aria_role(&mut self, _role: &str) {}
2583    fn set_aria_label(&mut self, _label: &str) {}
2584    fn set_aria_valuemin(&mut self, _min: f32) {}
2585    fn set_aria_valuemax(&mut self, _max: f32) {}
2586    fn set_aria_valuenow(&mut self, _now: f32) {}
2587
2588    /// Push a focus trap onto the stack. While active, keyboard focus is
2589    /// trapped within the specified element and its children.
2590    /// Returns a trap ID that must be passed to `pop_focus_trap`.
2591    fn push_focus_trap(&mut self, _element_id: &str) -> u64 { 0 }
2592
2593    /// Pop the most recently pushed focus trap.
2594    fn pop_focus_trap(&mut self, _trap_id: u64) {}
2595
2596    /// Register a shared element for Bifrost Bridge transitions.
2597    fn register_shared_element(&mut self, _id: &str, _rect: Rect) {}
2598
2599    /// Set a unique key for the current VDOM node to ensure stable identity during diffing.
2600    fn set_key(&mut self, _key: &str) {}
2601
2602    // ── Telemetry ────────────────────────────────────────────────────────
2603    /// Get real-time performance telemetry.
2604    fn get_telemetry(&self) -> TelemetryData {
2605        TelemetryData::default()
2606    }
2607
2608    // ── GPU State Management ─────────────────────────────────────────────
2609    /// Push a shadow state to the stack. All following draw calls will have this shadow.
2610    fn push_shadow(&mut self, _radius: f32, _color: [f32; 4], _offset: [f32; 2]) {}
2611    /// Pop the last shadow state from the stack.
2612    fn pop_shadow(&mut self) {}
2613
2614    // ── VDOM & Scene Graph ───────────────────────────────────────────────
2615    /// Push a Virtual DOM node onto the stack for hierarchy tracking.
2616    fn push_vnode(&mut self, _rect: Rect, _name: &'static str) {}
2617    /// Pop the current Virtual DOM node from the stack.
2618    fn pop_vnode(&mut self) {}
2619    /// Register an event handler for the current VDOM node.
2620    fn register_handler(
2621        &mut self,
2622        _event_type: &str,
2623        _handler: std::sync::Arc<dyn Fn(Event) + Send + Sync>,
2624    ) {
2625    }
2626
2627    // ── Z-Index & Depth ──────────────────────────────────────────────────
2628    /// Set the current Z-index for depth sorting.
2629    /// Higher values appear closer to the viewer.
2630    fn set_z_index(&mut self, _z: f32) {}
2631    /// Get the current Z-index.
2632    fn get_z_index(&self) -> f32 {
2633        0.0
2634    }
2635
2636    // ── Vector Graphics ──────────────────────────────────────────────────
2637    /// Load an SVG model from raw bytes.
2638    fn load_svg(&mut self, _name: &str, _svg_data: &[u8]) {}
2639    /// Draw a pre-loaded SVG model.
2640    fn draw_svg(&mut self, _name: &str, _rect: Rect) {}
2641    /// Draw a pre-loaded SVG model with a per-instance animation time offset.
2642    /// The offset shifts the animation phase, allowing multiple draws of the same
2643    /// SVG to animate independently. Default delegates to draw_svg (no offset).
2644    fn draw_svg_with_offset(&mut self, name: &str, rect: Rect, _animation_time_offset: f32) {
2645        Renderer::draw_svg(self, name, rect);
2646    }
2647    /// Draw a pre-loaded SVG model with explicit draw_order for z-sorting.
2648    /// draw_order=200 renders above UI chrome (draw_order=0).
2649    fn draw_svg_with_order(&mut self, name: &str, rect: Rect, _draw_order: i32) {
2650        Renderer::draw_svg(self, name, rect);
2651    }
2652    /// Serialize a pre-loaded SVG model back to SVG XML markup.
2653    /// Returns the serialized SVG string, or an error if the model is not loaded
2654    /// or serialization is not supported by this renderer.
2655    fn serialize_svg(&mut self, _name: &str) -> Result<String, String> {
2656        Err("SVG serialization not supported by this renderer".into())
2657    }
2658    /// Apply an SVG filter to a pre-loaded SVG model by filter element ID.
2659    /// The filter is evaluated and the result composited back into the SVG.
2660    /// Returns the filtered SVG as XML, or an error if not supported.
2661    fn apply_svg_filter(
2662        &mut self,
2663        _name: &str,
2664        _filter_id: &str,
2665        _region: Rect,
2666    ) -> Result<String, String> {
2667        Err("SVG filter not supported by this renderer".into())
2668    }
2669
2670    // ── GPU Transformations ──────────────────────────────────────────────
2671    /// Push a 2D transform (translation, scale, rotation) onto the stack.
2672    /// This transform should be applied to all subsequent draw calls until popped.
2673    /// Transform-only animations use this to avoid re-triggering the layout engine.
2674    fn push_transform(&mut self, _translation: [f32; 2], _scale: [f32; 2], _rotation: f32) {}
2675    /// Push a raw 2D affine transform matrix [a, b, c, d, e, f] corresponding to
2676    /// [m11, m12, m21, m22, tx, ty].
2677    fn push_affine(&mut self, _transform: [f32; 6]) {}
2678    /// Pop the last 2D transform from the stack.
2679    fn pop_transform(&mut self) {}
2680    /// Return the resolved layout bounds for a specific node ID if it exists.
2681    fn query_layout(&self, _node_id: scene_graph::NodeId) -> Option<Rect> {
2682        None
2683    }
2684    /// Enable or disable the layout debug overlay (bounds, padding, margin).
2685    fn set_debug_layout(&mut self, _enabled: bool) {}
2686    /// Check if the layout debug overlay is currently enabled.
2687    fn get_debug_layout(&self) -> bool {
2688        false
2689    }
2690
2691    // ── Material Routing ─────────────────────────────────────────────────
2692    /// Set the active material for subsequent draw calls.
2693    /// Controls which pass a draw call is routed to in the multi-pass pipeline.
2694    fn set_material(&mut self, _material: crate::material::DrawMaterial) {}
2695    /// Return the currently active material (defaults to Opaque).
2696    fn current_material(&self) -> crate::material::DrawMaterial {
2697        crate::material::DrawMaterial::Opaque
2698    }
2699
2700    // ── Vili Interaction Paradigm ──────────────────────────────────────────
2701    /// Compute the user's velocity/intent vector.
2702    fn mimir_intent(&self) -> [f32; 2] {
2703        [0.0, 0.0]
2704    }
2705    /// Calculate magnetic coordinate warp towards an anchor.
2706    fn magnetic_warp(&self, pointer: [f32; 2], anchor_rect: Rect, strength: f32) -> [f32; 2] {
2707        if strength <= 0.0 {
2708            return pointer;
2709        }
2710        let cx = anchor_rect.x + anchor_rect.width / 2.0;
2711        let cy = anchor_rect.y + anchor_rect.height / 2.0;
2712        let dx = pointer[0] - cx;
2713        let dy = pointer[1] - cy;
2714        let dist = (dx * dx + dy * dy).sqrt();
2715        let radius = 120.0;
2716        if dist < radius && dist > 0.0 {
2717            let force = (1.0 - dist / radius) * strength;
2718            [pointer[0] - dx * force, pointer[1] - dy * force]
2719        } else {
2720            pointer
2721        }
2722    }
2723    /// Calculate kinematic glow intensity based on proximity.
2724    fn mani_glow_intensity(&self, pointer: [f32; 2], bounds: Rect, radius: f32) -> f32 {
2725        let cx = bounds.x + bounds.width / 2.0;
2726        let cy = bounds.y + bounds.height / 2.0;
2727        let dist = ((pointer[0] - cx).powi(2) + (pointer[1] - cy).powi(2)).sqrt();
2728        if dist < radius {
2729            (1.0 - dist / radius).clamp(0.0, 1.0)
2730        } else {
2731            0.0
2732        }
2733    }
2734    /// Calculate dynamic element attention (scaling/morphing) statelessly per frame.
2735    fn fafnir_evolve(&self, pointer: [f32; 2], bounds: Rect, max_scale: f32) -> f32 {
2736        let prox = self.mani_glow_intensity(pointer, bounds, 120.0);
2737        1.0 + (max_scale - 1.0) * prox
2738    }
2739    /// Sets the precise Vili SDF Shape boundary for hit-testing.
2740    fn set_sdf_shape(&mut self, _shape: crate::layout::SdfShape) {}
2741
2742    // -- Portal / PhaseGate rendering -----------------------------------------
2743
2744    /// Begin rendering into the portal root layer instead of the inline tree.
2745    /// All draw calls between `enter_portal` and `exit_portal` are collected
2746    /// into a separate buffer that is composited AFTER the main tree.
2747    ///
2748    /// WHY separate buffer: The main tree may have clipping, transforms, or
2749    /// opacity that should NOT affect overlays. The portal layer renders on top
2750    /// of everything, ignoring the local coordinate system.
2751    fn enter_portal(&mut self, _z_index: i32) {}
2752
2753    /// Exit the portal layer and return to inline rendering.
2754    /// The portal content collected since `enter_portal` is now sealed --
2755    /// no more draw calls will be appended to it.
2756    fn exit_portal(&mut self) {}
2757
2758    /// Get the current viewport size in logical pixels.
2759    /// Used by portal content to size itself to the full screen.
2760    fn viewport_size(&self) -> Rect {
2761        Rect::new(0.0, 0.0, 1920.0, 1080.0)
2762    }
2763
2764    // -- Accessibility announcements -----------------------------------------
2765
2766    /// Announce a message to screen readers via the platform accessibility API.
2767    /// This call is non-blocking. The message is queued and the screen reader
2768    /// will speak it at its own pace.
2769    fn announce(&mut self, _message: &str, _priority: AnnouncementPriority) {}
2770}
2771
2772/// Utility for accessibility compliance (WCAG 2.1).
2773pub mod accessibility {
2774    /// Calculate the relative luminance of an sRGB color.
2775    pub fn relative_luminance(color: [f32; 4]) -> f32 {
2776        let f = |c: f32| {
2777            if c <= 0.03928 {
2778                c / 12.92
2779            } else {
2780                ((c + 0.055) / 1.055).powf(2.4)
2781            }
2782        };
2783        0.2126 * f(color[0]) + 0.7152 * f(color[1]) + 0.0722 * f(color[2])
2784    }
2785
2786    /// Calculate the contrast ratio between two colors.
2787    pub fn contrast_ratio(c1: [f32; 4], c2: [f32; 4]) -> f32 {
2788        let l1 = relative_luminance(c1);
2789        let l2 = relative_luminance(c2);
2790        let (light, dark) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
2791        (light + 0.05) / (dark + 0.05)
2792    }
2793}
2794/// Defines the hardware acceleration tier and feature set available to the renderer.
2795#[derive(
2796    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
2797)]
2798pub enum RenderTier {
2799    /// High-performance GPU path (WebGPU / Vulkan / Metal / DX12) with full shader support.
2800    Tier1GPU = 0,
2801    /// Mid-tier GPU path (WebGL2 / OpenGL 3.3) with standard shader support.
2802    Tier2GPU = 1,
2803    /// Fallback software or basic hardware path (Canvas 2D / GDI+) with limited effects.
2804    Tier3Fallback = 2,
2805}
2806// =============================================================================
2807// BERSERKER UNIFORMS
2808// =============================================================================
2809use bytemuck::{Pod, Zeroable};
2810/// Fully themeable color palette for the Berserker pipeline.
2811#[repr(C)]
2812#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
2813pub struct ColorTheme {
2814    pub primary_neon: [f32; 4], // (R, G, B, intensity)
2815    pub shatter_neon: [f32; 4],
2816    pub glass_base: [f32; 4],
2817    pub glass_edge: [f32; 4],
2818    pub rune_glow: [f32; 4],
2819    pub ember_core: [f32; 4],
2820    pub background_deep: [f32; 4],
2821    pub mani_glow: [f32; 4], // (R, G, B, radius)
2822    pub glass_blur_strength: f32,
2823    pub shatter_edge_width: f32,
2824    pub neon_bloom_radius: f32,
2825    pub rune_opacity: f32,
2826    /// Weight of adaptive tint from backdrop [0.0, 1.0].
2827    /// 0.0 = static theme tint, 1.0 = fully adaptive.
2828    pub glass_tint_adapt: f32,
2829    /// Per-frame glass IOR override. 0.0 = use shader default (1.45).
2830    pub glass_ior: f32,
2831    /// Color space for framebuffer output. 0 = sRGB (default), 1 = Display P3, 2 = Adobe RGB.
2832    pub color_space: u32,
2833    // Padding to match WGSL uniform buffer 16-byte struct alignment (total = 176 bytes).
2834    pub _pad0: f32,
2835    pub _pad1: f32,
2836    pub _pad2: f32,
2837    pub _pad3: f32,
2838    pub _pad4: f32,
2839}
2840// P2-9: Compile-time layout verification between Rust ColorTheme and WGSL.
2841// WGSL std140 struct size = 176 bytes (164 raw + 12 alignment padding).
2842// Rust repr(C) struct must match exactly.
2843const _: () = assert!(
2844    std::mem::size_of::<ColorTheme>() == 176,
2845    "ColorTheme Rust/WGSL layout mismatch: expected 176 bytes"
2846);
2847impl ColorTheme {
2848    /// Asgard Mode: The high-fidelity "Cyberpunk Viking" aesthetic.
2849    pub fn asgard() -> Self {
2850        Self {
2851            primary_neon: [0.0, 1.0, 0.95, 1.2],
2852            shatter_neon: [1.0, 0.0, 0.75, 1.5],
2853            glass_base: [0.04, 0.04, 0.06, 0.82],
2854            glass_edge: [0.0, 0.45, 0.55, 0.6],
2855            rune_glow: [0.75, 0.98, 1.0, 0.9],
2856            ember_core: [0.95, 0.12, 0.12, 1.0],
2857            background_deep: [0.01, 0.01, 0.03, 1.0],
2858            mani_glow: [0.7, 0.9, 1.0, 0.05],
2859            glass_blur_strength: 0.6,
2860            shatter_edge_width: 1.8,
2861            neon_bloom_radius: 0.022,
2862            rune_opacity: 0.55,
2863            glass_tint_adapt: 0.35,
2864            glass_ior: 1.45,
2865            color_space: 0,
2866            _pad0: 0.0,
2867            _pad1: 0.0,
2868            _pad2: 0.0,
2869            _pad3: 0.0,
2870            _pad4: 0.0,
2871        }
2872    }
2873
2874    /// Midgard Mode: A clean, functional tactical HUD for standard operations.
2875    pub fn midgard() -> Self {
2876        Self {
2877            primary_neon: [0.2, 0.4, 0.6, 1.0], // Muted blue
2878            shatter_neon: [0.5, 0.5, 0.5, 1.0], // Neutral gray
2879            glass_base: [0.1, 0.12, 0.15, 1.0], // Solid slate
2880            glass_edge: [0.3, 0.35, 0.4, 1.0],  // Subtle border
2881            rune_glow: [0.8, 0.8, 0.8, 0.0],    // Runes disabled
2882            ember_core: [0.5, 0.5, 0.5, 1.0],
2883            background_deep: [0.05, 0.05, 0.07, 1.0],
2884            mani_glow: [0.0, 0.0, 0.0, 0.0], // No cursor glow
2885            glass_blur_strength: 0.0,        // No blur
2886            shatter_edge_width: 1.0,
2887            neon_bloom_radius: 0.0,
2888            rune_opacity: 0.0,
2889            glass_tint_adapt: 0.0,
2890            glass_ior: 1.0,
2891            color_space: 0,
2892            _pad0: 0.0,
2893            _pad1: 0.0,
2894            _pad2: 0.0,
2895            _pad3: 0.0,
2896            _pad4: 0.0,
2897        }
2898    }
2899
2900    pub fn cyberpunk_viking() -> Self {
2901        Self::asgard()
2902    }
2903    pub fn vibrant_glass() -> Self {
2904        Self {
2905            primary_neon: [0.0, 1.0, 0.95, 1.2],
2906            shatter_neon: [1.0, 0.0, 0.75, 1.5],
2907            glass_base: [0.55, 0.6, 0.7, 0.08], // Luminous cool tint
2908            glass_edge: [0.7, 0.85, 1.0, 0.45], // Subtle blue-white rim
2909            rune_glow: [0.75, 0.98, 1.0, 0.9],
2910            ember_core: [1.0, 0.4, 0.1, 1.0],
2911            background_deep: [0.05, 0.05, 0.1, 1.0],
2912            mani_glow: [0.7, 0.9, 1.0, 0.05],
2913            glass_blur_strength: 0.9,
2914            shatter_edge_width: 1.8,
2915            neon_bloom_radius: 0.022,
2916            rune_opacity: 0.55,
2917            glass_tint_adapt: 0.65,
2918            glass_ior: 1.45,
2919            color_space: 0,
2920            _pad0: 0.0,
2921            _pad1: 0.0,
2922            _pad2: 0.0,
2923            _pad3: 0.0,
2924            _pad4: 0.0,
2925        }
2926    }
2927
2928    /// Berserker Mode: Blood-iron neon, aggressive contrast, forge-heated glass.
2929    pub fn berserker() -> Self {
2930        Self {
2931            primary_neon: [1.0, 0.08, 0.12, 1.8],
2932            shatter_neon: [0.95, 0.92, 0.88, 1.6],
2933            glass_base: [0.03, 0.02, 0.02, 0.88],
2934            glass_edge: [0.8, 0.35, 0.08, 0.7],
2935            rune_glow: [0.9, 0.72, 0.3, 1.0],
2936            ember_core: [0.98, 0.25, 0.05, 1.0],
2937            background_deep: [0.01, 0.005, 0.005, 1.0],
2938            mani_glow: [0.8, 0.2, 0.05, 0.08],
2939            glass_blur_strength: 0.85,
2940            shatter_edge_width: 2.8,
2941            neon_bloom_radius: 0.035,
2942            rune_opacity: 0.85,
2943            glass_tint_adapt: 0.15,
2944            glass_ior: 1.85,
2945            color_space: 0,
2946            _pad0: 0.0,
2947            _pad1: 0.0,
2948            _pad2: 0.0,
2949            _pad3: 0.0,
2950            _pad4: 0.0,
2951        }
2952    }
2953}
2954impl Default for ColorTheme {
2955    fn default() -> Self {
2956        Self::vibrant_glass()
2957    }
2958}
2959/// Per-frame scene state for the Berserker pipeline.
2960#[repr(C)]
2961#[derive(Copy, Clone, Debug, Pod, Zeroable, serde::Serialize, serde::Deserialize)]
2962pub struct SceneUniforms {
2963    pub view: glam::Mat4,
2964    pub proj: glam::Mat4,
2965    pub time: f32,
2966    pub delta_time: f32,
2967    pub resolution: [f32; 2],
2968    pub mouse: [f32; 2],
2969    pub mouse_velocity: [f32; 2],
2970    pub shatter_origin: [f32; 2],
2971    pub shatter_time: f32,
2972    pub shatter_force: f32,
2973    pub berzerker_rage: f32,
2974    pub berzerker_mode: u32,
2975    pub scroll_offset: f32,
2976    pub scale_factor: f32,
2977    pub scene_type: u32,
2978    pub _pad_vec2_align: [u32; 1], // 4-byte pad: WGSL vec2<f32> requires 8-byte alignment
2979    pub fireball_pos: [f32; 2],
2980    pub _pad: [f32; 4], // Align to 224 bytes (struct align 16 from Mat4)
2981}
2982
2983pub const SCENE_AURORA: u32 = 0;
2984pub const SCENE_VOID: u32 = 1;
2985pub const SCENE_NEBULA: u32 = 2;
2986pub const SCENE_GLITCH: u32 = 3;
2987pub const SCENE_YGGDRASIL: u32 = 4;
2988
2989/// Resolve a scene name string to a scene preset constant.
2990/// Case-insensitive. Supports: "aurora", "void", "nebula", "glitch", "yggdrasil".
2991/// Also supports common aliases: "empty", "none" → VOID.
2992/// Returns None if the name is not recognized.
2993pub fn resolve_scene_by_name(name: &str) -> Option<u32> {
2994    let normalized = name.to_lowercase().replace(['-', '_', ' ', '.'], "");
2995    match normalized.as_str() {
2996        "aurora" => Some(SCENE_AURORA),
2997        "void" | "empty" | "none" | "blank" => Some(SCENE_VOID),
2998        "nebula" => Some(SCENE_NEBULA),
2999        "glitch" => Some(SCENE_GLITCH),
3000        "yggdrasil" | "worldtree" | "tree" => Some(SCENE_YGGDRASIL),
3001        _ => None,
3002    }
3003}
3004
3005impl SceneUniforms {
3006    pub fn new(width: f32, height: f32) -> Self {
3007        Self {
3008            view: glam::Mat4::IDENTITY,
3009            proj: glam::Mat4::orthographic_lh(0.0, width, height, 0.0, -100.0, 100.0),
3010            time: 0.0,
3011            delta_time: 0.016,
3012            resolution: [width, height],
3013            mouse: [0.5, 0.5],
3014            mouse_velocity: [0.0, 0.0],
3015            shatter_origin: [0.5, 0.5],
3016            shatter_time: -100.0,
3017            shatter_force: 0.0,
3018            berzerker_rage: 0.0,
3019            berzerker_mode: 0,
3020            scroll_offset: 0.0,
3021            scale_factor: 1.0,
3022            scene_type: SCENE_AURORA,
3023            _pad_vec2_align: [0],
3024            fireball_pos: [0.0, 0.0],
3025            _pad: [0.0; 4],
3026        }
3027    }
3028}
3029/// A 3D mesh containing vertex and index data.
3030#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
3031pub struct Mesh {
3032    pub vertices: Vec<[f32; 3]>,
3033    pub normals: Vec<[f32; 3]>,
3034    pub indices: Vec<u32>,
3035}
3036impl Mesh {
3037    pub fn from_obj(data: &[u8]) -> anyhow::Result<Vec<Self>> {
3038        let mut cursor = std::io::Cursor::new(data);
3039        let (models, _) = tobj::load_obj_buf(&mut cursor, &tobj::LoadOptions::default(), |_| {
3040            Ok((Vec::new(), Default::default()))
3041        })?;
3042        let mut meshes = Vec::new();
3043        for m in models {
3044            let mesh = m.mesh;
3045            let vertices: Vec<[f32; 3]> = mesh
3046                .positions
3047                .chunks_exact(3)
3048                .map(|c| [c[0], c[1], c[2]])
3049                .collect();
3050            let normals = if mesh.normals.is_empty() {
3051                vec![[0.0, 0.0, 1.0]; vertices.len()]
3052            } else {
3053                mesh.normals.chunks(3).map(|c| [c[0], c[1], c[2]]).collect()
3054            };
3055            meshes.push(Mesh {
3056                vertices,
3057                normals,
3058                indices: mesh.indices,
3059            });
3060        }
3061        Ok(meshes)
3062    }
3063    pub fn from_stl(data: &[u8]) -> anyhow::Result<Self> {
3064        let mut cursor = std::io::Cursor::new(data);
3065        let stl = stl_io::read_stl(&mut cursor)?;
3066        let vertices: Vec<[f32; 3]> = stl.vertices.iter().map(|v| [v[0], v[1], v[2]]).collect();
3067        let mut indices = Vec::new();
3068        for face in stl.faces {
3069            indices.push(face.vertices[0] as u32);
3070            indices.push(face.vertices[1] as u32);
3071            indices.push(face.vertices[2] as u32);
3072        }
3073        let normals = vec![[0.0, 0.0, 1.0]; vertices.len()];
3074        Ok(Mesh {
3075            vertices,
3076            normals,
3077            indices,
3078        })
3079    }
3080}
3081
3082// ══════════════════════════════════════════════════════════════════════════
3083// 3D TYPES -- Phase 1: Camera, Transform, and 2.5D layer support
3084// ══════════════════════════════════════════════════════════════════════════
3085
3086/// A 3D transform: position, rotation (quaternion), and scale.
3087#[derive(Debug, Clone, Copy, PartialEq)]
3088pub struct Transform3D {
3089    pub position: glam::Vec3,
3090    pub rotation: glam::Quat,
3091    pub scale: glam::Vec3,
3092}
3093
3094impl Default for Transform3D {
3095    fn default() -> Self {
3096        Self {
3097            position: glam::Vec3::ZERO,
3098            rotation: glam::Quat::IDENTITY,
3099            scale: glam::Vec3::ONE,
3100        }
3101    }
3102}
3103
3104impl Transform3D {
3105    /// Convert this transform to a 4x4 model matrix.
3106    pub fn to_matrix(&self) -> glam::Mat4 {
3107        glam::Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position)
3108    }
3109
3110    /// Create a 2D-compatible transform (z=0, no rotation on z axis).
3111    pub fn from_2d(x: f32, y: f32, rotation: f32) -> Self {
3112        Self {
3113            position: glam::Vec3::new(x, y, 0.0),
3114            rotation: glam::Quat::from_rotation_z(rotation),
3115            scale: glam::Vec3::ONE,
3116        }
3117    }
3118}
3119
3120/// Camera definition for 3D rendering.
3121#[derive(Debug, Clone, Copy)]
3122pub struct Camera3D {
3123    /// World-space camera position.
3124    pub position: glam::Vec3,
3125    /// World-space point the camera looks at.
3126    pub target: glam::Vec3,
3127    /// World-space up vector.
3128    pub up: glam::Vec3,
3129    /// Field of view in radians (perspective) or half-height (orthographic).
3130    pub fov_y: f32,
3131    /// Near clipping plane distance.
3132    pub near: f32,
3133    /// Far clipping plane distance.
3134    pub far: f32,
3135    /// If true, use perspective projection. If false, use orthographic.
3136    pub perspective: bool,
3137    /// Aspect ratio (width / height). Used for perspective projection.
3138    pub aspect: f32,
3139}
3140
3141/// Material properties for 3D rendering.
3142#[derive(Debug, Clone, Copy, PartialEq)]
3143pub struct Material3D {
3144    /// Base color (RGBA).
3145    pub base_color: [f32; 4],
3146    /// Metallic factor (0 = dielectric, 1 = metallic).
3147    pub metallic: f32,
3148    /// Roughness factor (0 = mirror, 1 = fully diffuse).
3149    pub roughness: f32,
3150    /// Emissive color (RGB) for self-illumination.
3151    pub emissive: [f32; 3],
3152    /// Opacity (0 = transparent, 1 = opaque).
3153    pub opacity: f32,
3154}
3155
3156impl Default for Material3D {
3157    fn default() -> Self {
3158        Self {
3159            base_color: [1.0, 1.0, 1.0, 1.0],
3160            metallic: 0.0,
3161            roughness: 0.5,
3162            emissive: [0.0, 0.0, 0.0],
3163            opacity: 1.0,
3164        }
3165    }
3166}
3167
3168impl Material3D {
3169    /// Create a simple unlit material with just a color.
3170    pub fn unlit(color: [f32; 4]) -> Self {
3171        Self {
3172            base_color: color,
3173            metallic: 0.0,
3174            roughness: 1.0,
3175            emissive: [0.0, 0.0, 0.0],
3176            opacity: color[3],
3177        }
3178    }
3179
3180    /// Create a metallic material.
3181    pub fn metallic(color: [f32; 4], roughness: f32) -> Self {
3182        Self {
3183            base_color: color,
3184            metallic: 1.0,
3185            roughness: roughness.clamp(0.0, 1.0),
3186            emissive: [0.0, 0.0, 0.0],
3187            opacity: color[3],
3188        }
3189    }
3190}
3191
3192impl Default for Camera3D {
3193    fn default() -> Self {
3194        Self {
3195            position: glam::Vec3::new(0.0, 0.0, 10.0),
3196            target: glam::Vec3::ZERO,
3197            up: glam::Vec3::Y,
3198            fov_y: 45.0f32.to_radians(),
3199            near: 0.1,
3200            far: 1000.0,
3201            perspective: true,
3202            aspect: 16.0 / 9.0,
3203        }
3204    }
3205}
3206
3207impl Camera3D {
3208    /// Compute the view matrix (world → camera space).
3209    pub fn view_matrix(&self) -> glam::Mat4 {
3210        glam::Mat4::look_at_lh(self.position, self.target, self.up)
3211    }
3212
3213    /// Compute the projection matrix.
3214    pub fn projection_matrix(&self) -> glam::Mat4 {
3215        if self.perspective {
3216            glam::Mat4::perspective_lh(self.fov_y, self.aspect, self.near, self.far)
3217        } else {
3218            // Orthographic with fov_y as half-height
3219            let top = self.fov_y;
3220            let right = top * self.aspect;
3221            glam::Mat4::orthographic_lh(-right, right, -top, top, self.near, self.far)
3222        }
3223    }
3224
3225    /// Compute the combined view-projection matrix.
3226    pub fn view_projection(&self) -> glam::Mat4 {
3227        self.projection_matrix() * self.view_matrix()
3228    }
3229}
3230
3231/// FrameRenderer extends Renderer with frame lifecycle management.
3232/// It is typically implemented by the host windowing/rendering environment.
3233pub trait FrameRenderer<E = ()>: Renderer {
3234    fn begin_frame(&mut self) -> E;
3235    fn render_frame(&mut self) {
3236        // Default implementation does nothing - override for custom frame rendering
3237    }
3238    fn end_frame(&mut self, encoder: E);
3239}
3240use std::sync::Arc;
3241type SubscriberList<T> = Arc<std::sync::Mutex<Vec<Box<dyn Fn(&T) + Send + Sync>>>>;
3242
3243/// P1-15 fix: invoke all subscribers in a list, isolating panics so that a
3244/// single faulty callback does not poison the Mutex and break all future
3245/// state updates forever. Returns the number of subscribers invoked
3246/// successfully. Each callback is wrapped in `catch_unwind`; panics are
3247/// logged but do not propagate.
3248fn invoke_subscribers_safely<T>(subs: &SubscriberList<T>, val: &T) -> usize
3249where
3250    // No UnwindSafe bound on T: subscriber callbacks receive &T and the
3251    // user is responsible for the panic-safety contract. We use
3252    // AssertUnwindSafe internally to opt out of the check.
3253{
3254    // Acquire the lock with poison recovery: if a previous panic poisoned
3255    // the mutex, recover and continue (the previous subscriber may have
3256    // left the list in an inconsistent state, but the best we can do is
3257    // log and try again). On recovery, the existing subscriber list is
3258    // preserved so we do not silently drop user subscriptions.
3259    let guard = match subs.lock() {
3260        Ok(g) => g,
3261        Err(poisoned) => {
3262            log::warn!(
3263                "[State] subscriber list mutex was poisoned; recovering"
3264            );
3265            poisoned.into_inner()
3266        }
3267    };
3268    let mut invoked = 0usize;
3269    for cb in guard.iter() {
3270        // Wrap each callback in catch_unwind so a panicking subscriber
3271        // does not poison the mutex and break subsequent state updates.
3272        // The catch_unwind returns Err if the closure panicked.
3273        let cb_ref: &(dyn Fn(&T) + Send + Sync) = &**cb;
3274        // Use AssertUnwindSafe because subscriber callbacks are Fn (not
3275        // UnwindSafe by default due to &T parameter), but the actual
3276        // panic-safety contract is the subscriber author's responsibility.
3277        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3278            cb_ref(val);
3279        }));
3280        if let Err(payload) = result {
3281            let msg = if let Some(s) = payload.downcast_ref::<&'static str>() {
3282                (*s).to_string()
3283            } else if let Some(s) = payload.downcast_ref::<String>() {
3284                s.clone()
3285            } else {
3286                "unknown panic payload".to_string()
3287            };
3288            log::error!("[State] subscriber callback panicked: {msg}");
3289            // Do NOT re-raise; continue invoking remaining subscribers.
3290        } else {
3291            invoked += 1;
3292        }
3293    }
3294    invoked
3295}
3296/// State wrapper that owns a value and notifies subscribers when changed.
3297///
3298/// P1-14: this struct carries 4 storage mechanisms:
3299/// 1. `arc_swap::ArcSwap<T>` for lock-free reads (the hot path)
3300/// 2. `arc_swap::ArcSwap<Option<MutationMetadata>>` for metadata reads
3301/// 3. `stm::TVar<T>` for atomic compound transactions (only on non-WASM)
3302/// 4. `stm::TVar<Option<MutationMetadata>>` for transactional metadata
3303///
3304/// The audit flagged this as 4 atomic/sync primitives per State<T>
3305/// instance, which is heavy for small states. The 4 mechanisms
3306/// are kept because they serve different purposes: arc_swap is
3307/// for the read-heavy hot path, TVar is for atomic compound
3308/// transactions. A future refactor could consolidate to a single
3309/// storage backend (e.g., always use TVar) but that would have a
3310/// performance cost on reads.
3311///
3312/// The `set()` method provides a way to bypass TVar for simple
3313/// single-value updates, avoiding the storage cost when compound
3314/// transactions aren't needed.
3315#[derive(Clone)]
3316pub struct State<T: Clone + Send + Sync + 'static> {
3317    swap: Arc<arc_swap::ArcSwap<T>>,
3318    metadata_swap: Arc<arc_swap::ArcSwap<Option<agents::MutationMetadata>>>,
3319    #[cfg(not(target_arch = "wasm32"))]
3320    tvar: Arc<stm::TVar<T>>,
3321    #[cfg(not(target_arch = "wasm32"))]
3322    metadata_tvar: Arc<stm::TVar<Option<agents::MutationMetadata>>>,
3323    subscribers: SubscriberList<T>,
3324    version: Arc<std::sync::atomic::AtomicU64>,
3325    resolution: agents::ConflictResolution,
3326}
3327impl<T: Clone + Send + Sync + 'static> State<T> {
3328    /// Create a new State with initial value
3329    pub fn new(value: T) -> Self {
3330        #[cfg(not(target_arch = "wasm32"))]
3331        let tvar = Arc::new(stm::TVar::new(value.clone()));
3332        #[cfg(not(target_arch = "wasm32"))]
3333        let metadata_tvar = Arc::new(stm::TVar::new(None));
3334        Self {
3335            swap: Arc::new(arc_swap::ArcSwap::from_pointee(value)),
3336            metadata_swap: Arc::new(arc_swap::ArcSwap::new(Arc::new(None))),
3337            #[cfg(not(target_arch = "wasm32"))]
3338            tvar,
3339            #[cfg(not(target_arch = "wasm32"))]
3340            metadata_tvar,
3341            subscribers: Arc::new(std::sync::Mutex::new(Vec::new())),
3342            version: Arc::new(std::sync::atomic::AtomicU64::new(0)),
3343            resolution: agents::ConflictResolution::default(),
3344        }
3345    }
3346    /// Set the conflict resolution strategy for this state.
3347    pub fn with_resolution(mut self, resolution: agents::ConflictResolution) -> Self {
3348        self.resolution = resolution;
3349        self
3350    }
3351    /// Get the current value
3352    pub fn get(&self) -> T {
3353        (**self.swap.load()).clone()
3354    }
3355    /// Set a new value, notifying all subscribers. Applies conflict resolution if agents are present.
3356    pub fn set(&self, value: T) {
3357        #[cfg(not(target_arch = "wasm32"))]
3358        let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3359            let new_meta = agents::get_current_mutation_metadata();
3360            let existing_meta = self.metadata_tvar.read(tx)?;
3361            let mut skip = false;
3362            if self.resolution == agents::ConflictResolution::PriorityWins
3363                && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3364                && new_m.priority < old_m.priority
3365            {
3366                skip = true;
3367            }
3368            if !skip {
3369                self.tvar.write(tx, value.clone())?;
3370                self.metadata_tvar.write(tx, new_meta)?;
3371                Ok((false, value.clone(), new_meta))
3372            } else {
3373                Ok((true, self.tvar.read(tx)?, existing_meta))
3374            }
3375        });
3376        #[cfg(target_arch = "wasm32")]
3377        let (was_skipped, final_val, final_meta) =
3378            (false, value, agents::get_current_mutation_metadata());
3379        if was_skipped {
3380            if let (Some(new_m), Some(old_m)) =
3381                (agents::get_current_mutation_metadata(), final_meta)
3382            {
3383                agents::notify_conflict(agents::ConflictEvent {
3384                    agent_id: new_m.agent_id,
3385                    priority: new_m.priority,
3386                    existing_agent_id: old_m.agent_id,
3387                    existing_priority: old_m.priority,
3388                    timestamp_ms: new_m.timestamp_ms,
3389                });
3390            }
3391            return;
3392        }
3393        self.swap.store(Arc::new(final_val.clone()));
3394        self.metadata_swap.store(Arc::new(final_meta));
3395        self.version
3396            .fetch_add(1, std::sync::atomic::Ordering::Release);
3397        let subs = Arc::clone(&self.subscribers);
3398        if crate::is_batching() {
3399            crate::enqueue_batch_task(Box::new(move || {
3400                let _ = invoke_subscribers_safely(&subs, &final_val);
3401            }));
3402        } else {
3403            let _ = invoke_subscribers_safely(&subs, &final_val);
3404        }
3405    }
3406
3407    /// P1-14: direct set that bypasses TVar for callers who don't
3408    /// need atomic compound transactions. Avoids the redundant
3409    /// storage cost when only the value and metadata are updated
3410    /// (not coordinated with other State<T> instances).
3411    ///
3412    /// Use this instead of `set()` when:
3413    ///  - You don't use conflict resolution (e.g., simple
3414    ///    single-threaded UI state).
3415    ///  - You don't need to coordinate with other State<T>
3416    ///    instances in a single transaction.
3417    ///
3418    /// Both the swap and TVar are updated atomically so that
3419    /// subsequent reads via either path see a consistent value.
3420    pub fn set_direct(&self, value: T) {
3421        self.swap.store(Arc::new(value.clone()));
3422        let new_meta = agents::get_current_mutation_metadata();
3423        self.metadata_swap.store(Arc::new(new_meta));
3424        self.version
3425            .fetch_add(1, std::sync::atomic::Ordering::Release);
3426        #[cfg(not(target_arch = "wasm32"))]
3427        {
3428            let _ = stm::atomically(|tx| {
3429                self.tvar.write(tx, value.clone())?;
3430                let meta = agents::get_current_mutation_metadata();
3431                self.metadata_tvar.write(tx, meta)?;
3432                Ok(())
3433            });
3434        }
3435        let subs = Arc::clone(&self.subscribers);
3436        if crate::is_batching() {
3437            crate::enqueue_batch_task(Box::new(move || {
3438                let _ = invoke_subscribers_safely(&subs, &value);
3439            }));
3440        } else {
3441            let _ = invoke_subscribers_safely(&subs, &value);
3442        }
3443    }
3444    pub fn mutate<F: Fn(&T) -> T>(&self, f: F) {
3445        #[cfg(not(target_arch = "wasm32"))]
3446        {
3447            let (was_skipped, final_val, final_meta) = stm::atomically(|tx| {
3448                let new_meta = agents::get_current_mutation_metadata();
3449                let existing_meta = self.metadata_tvar.read(tx)?;
3450                let mut skip = false;
3451                if self.resolution == agents::ConflictResolution::PriorityWins
3452                    && let (Some(new_m), Some(old_m)) = (new_meta, existing_meta)
3453                    && new_m.priority < old_m.priority
3454                {
3455                    skip = true;
3456                }
3457                if !skip {
3458                    let current = self.tvar.read(tx)?;
3459                    let next = f(&current);
3460                    self.tvar.write(tx, next.clone())?;
3461                    self.metadata_tvar.write(tx, new_meta)?;
3462                    Ok((false, next, new_meta))
3463                } else {
3464                    Ok((true, self.tvar.read(tx)?, existing_meta))
3465                }
3466            });
3467            if was_skipped {
3468                if let (Some(new_m), Some(old_m)) =
3469                    (agents::get_current_mutation_metadata(), final_meta)
3470                {
3471                    agents::notify_conflict(agents::ConflictEvent {
3472                        agent_id: new_m.agent_id,
3473                        priority: new_m.priority,
3474                        existing_agent_id: old_m.agent_id,
3475                        existing_priority: old_m.priority,
3476                        timestamp_ms: new_m.timestamp_ms,
3477                    });
3478                }
3479                return;
3480            }
3481            self.swap.store(Arc::new(final_val.clone()));
3482            self.metadata_swap.store(Arc::new(final_meta));
3483            self.version
3484                .fetch_add(1, std::sync::atomic::Ordering::Release);
3485            let subs = Arc::clone(&self.subscribers);
3486            if crate::is_batching() {
3487                crate::enqueue_batch_task(Box::new(move || {
3488                    let _ = invoke_subscribers_safely(&subs, &final_val);
3489                }));
3490            } else {
3491                let _ = invoke_subscribers_safely(&subs, &final_val);
3492            }
3493        }
3494        #[cfg(target_arch = "wasm32")]
3495        {
3496            self.set(f(&self.get()));
3497        }
3498    }
3499    /// Get current version
3500    pub fn version(&self) -> u64 {
3501        self.version.load(std::sync::atomic::Ordering::Acquire)
3502    }
3503    /// Subscribe to state changes
3504    pub fn subscribe<F: Fn(&T) + Send + Sync + 'static>(&self, callback: F) {
3505        self.subscribers.lock().unwrap_or_else(|p| p.into_inner()).push(Box::new(callback));
3506    }
3507}
3508use crate::runtime::NodeStateSnapshot;
3509use std::sync::OnceLock;
3510use std::sync::atomic::{AtomicBool, Ordering};
3511
3512/// P1-17 fix: shared fallback tokio runtime for `Suspense::new_async`.
3513///
3514/// When `new_async` is called without an ambient tokio runtime, the
3515/// previous implementation spawned a new OS thread + tokio runtime
3516/// for EACH call. For an app with many async data loads (e.g. a data
3517/// lake visualizer), this could spawn hundreds of OS threads.
3518///
3519/// The fix is a process-wide shared multi-threaded runtime, lazily
3520/// initialized on first use. The runtime uses a bounded worker count
3521/// (default: `max(1, num_cpus - 1)`) so we never spawn more than
3522/// `WORKER_THREADS` OS threads, regardless of how many Suspense
3523/// instances are created.
3524///
3525/// When the process exits the runtime is dropped, which joins all
3526/// worker threads.
3527#[cfg(not(target_arch = "wasm32"))]
3528static FALLBACK_RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
3529
3530/// Number of worker threads for the fallback runtime. Computed lazily
3531/// from the available CPU count, then cached.
3532#[cfg(not(target_arch = "wasm32"))]
3533static FALLBACK_WORKER_COUNT: OnceLock<usize> = OnceLock::new();
3534
3535#[cfg(not(target_arch = "wasm32"))]
3536fn fallback_runtime() -> &'static tokio::runtime::Runtime {
3537    FALLBACK_RUNTIME.get_or_init(|| {
3538        // Bounded worker count: leave at least one core for the
3539        // application, but cap at 8 to avoid runaway thread creation
3540        // on hosts with very high CPU counts.
3541        let worker_count = *FALLBACK_WORKER_COUNT.get_or_init(|| {
3542            let available = std::thread::available_parallelism()
3543                .map(|n| n.get())
3544                .unwrap_or(2);
3545            available.saturating_sub(1).clamp(1, 8)
3546        });
3547        tokio::runtime::Builder::new_current_thread()
3548            .worker_threads(worker_count)
3549            .thread_name("cvkg-fallback-rt")
3550            .enable_all()
3551            .build()
3552            .expect("failed to build fallback tokio runtime")
3553    })
3554}
3555/// Global application state registry.
3556pub static SYSTEM_STATE: OnceLock<Arc<arc_swap::ArcSwap<AppState>>> = OnceLock::new();
3557#[cfg(not(target_arch = "wasm32"))]
3558static KNOWLEDGE_TVAR: OnceLock<stm::TVar<AppState>> = OnceLock::new();
3559static IS_BATCHING: AtomicBool = AtomicBool::new(false);
3560pub static IS_RENDERING: AtomicBool = AtomicBool::new(false);
3561pub static LAYOUT_DIRTY: AtomicBool = AtomicBool::new(false);
3562type BatchQueue = OnceLock<std::sync::Mutex<Vec<Box<dyn FnOnce() + Send + Sync>>>>;
3563static BATCH_QUEUE: BatchQueue = OnceLock::new();
3564/// Global write lock to serialize updates to SYSTEM_STATE and KNOWLEDGE_TVAR,
3565/// preventing parallel race conditions between STM transactions and the lock-free reader state.
3566static STATE_WRITE_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
3567/// Returns true if state updates are currently being batched.
3568pub fn is_batching() -> bool {
3569    IS_BATCHING.load(Ordering::Acquire)
3570}
3571/// Returns true if the system is currently in the render phase.
3572pub fn is_rendering() -> bool {
3573    IS_RENDERING.load(Ordering::Acquire)
3574}
3575/// Signals the start of the render phase. Mutations during this phase trigger warnings.
3576pub fn begin_render_phase() {
3577    IS_RENDERING.store(true, Ordering::Release);
3578}
3579/// Signals the end of the render phase.
3580pub fn end_render_phase() {
3581    IS_RENDERING.store(false, Ordering::Release);
3582}
3583/// Enqueues a notification task to be run when the current batch flushes.
3584pub fn enqueue_batch_task(task: Box<dyn FnOnce() + Send + Sync>) {
3585    let mut queue = BATCH_QUEUE
3586        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3587        .lock()
3588        .unwrap_or_else(|p| p.into_inner());
3589    queue.push(task);
3590}
3591/// Executes multiple state updates in a single batch, deferring all subscriber
3592/// notifications until the closure completes. This prevents layout thrashing
3593/// and redundant render cycles when modifying multiple independent states.
3594pub fn batch<F: FnOnce()>(f: F) {
3595    if IS_BATCHING.swap(true, Ordering::AcqRel) {
3596        // Already inside a batch, just execute
3597        f();
3598        return;
3599    }
3600    f();
3601    IS_BATCHING.store(false, Ordering::Release);
3602    let mut queue = BATCH_QUEUE
3603        .get_or_init(|| std::sync::Mutex::new(Vec::new()))
3604        .lock()
3605        .unwrap();
3606    let tasks: Vec<_> = queue.drain(..).collect();
3607    drop(queue);
3608    for task in tasks {
3609        task();
3610    }
3611}
3612/// Get a reference to the global system state.
3613pub fn get_system_state() -> Arc<arc_swap::ArcSwap<AppState>> {
3614    SYSTEM_STATE
3615        .get_or_init(|| Arc::new(arc_swap::ArcSwap::from_pointee(AppState::default())))
3616        .clone()
3617}
3618pub fn load_system_state() -> arc_swap::Guard<Arc<AppState>> {
3619    get_system_state().load()
3620}
3621pub fn update_system_state<F>(f: F)
3622where
3623    F: FnOnce(&AppState) -> AppState,
3624{
3625    let _lock = STATE_WRITE_MUTEX.lock().unwrap_or_else(|p| p.into_inner());
3626    if is_rendering() {
3627        log::warn!(
3628            "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3629        );
3630    }
3631    LAYOUT_DIRTY.store(true, Ordering::SeqCst);
3632    let swap = get_system_state();
3633    let current = swap.load();
3634    let new_state = Arc::new(f(&current));
3635    swap.store(Arc::clone(&new_state));
3636    #[cfg(not(target_arch = "wasm32"))]
3637    {
3638        let tvar = KNOWLEDGE_TVAR.get_or_init(|| stm::TVar::new((*new_state).clone()));
3639        stm::atomically(|tx| tvar.write(tx, (*new_state).clone()));
3640    }
3641}
3642pub fn transact_system_state<F>(f: F)
3643where
3644    F: Fn(&AppState) -> AppState,
3645{
3646    let _lock = STATE_WRITE_MUTEX.lock().unwrap_or_else(|p| p.into_inner());
3647    #[cfg(not(target_arch = "wasm32"))]
3648    {
3649        if is_rendering() {
3650            log::warn!(
3651                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3652            );
3653        }
3654        let tvar = KNOWLEDGE_TVAR
3655            .get_or_init(|| stm::TVar::new((**get_system_state().load()).clone()))
3656            .clone();
3657        let new_state = stm::atomically(move |tx| {
3658            let current = tvar.read(tx)?;
3659            let next = f(&current);
3660            tvar.write(tx, next.clone())?;
3661            Ok(next)
3662        });
3663        get_system_state().store(Arc::new(new_state));
3664    }
3665    #[cfg(target_arch = "wasm32")]
3666    {
3667        if is_rendering() {
3668            log::warn!(
3669                "LAYOUT THRASH DETECTED: System state mutated during render phase. This may trigger redundant layout passes and impact performance."
3670            );
3671        }
3672        update_system_state(f);
3673    }
3674}
3675impl AppState {
3676    /// Create a new empty AppState.
3677    pub fn new() -> Self {
3678        Self::default()
3679    }
3680    /// Set a component's internal state.
3681    pub fn set_component_state<T: 'static + Send + Sync>(&mut self, id: u64, state: T) {
3682        self.component_states
3683            .insert(id, Arc::new(std::sync::RwLock::new(state)));
3684    }
3685    /// Get a reference to a component's internal state.
3686    pub fn get_component_state<T: 'static + Send + Sync>(
3687        &self,
3688        id: u64,
3689    ) -> Option<Arc<std::sync::RwLock<T>>> {
3690        let stored = self.component_states.get(&id)?;
3691        // X-01 fix: safe downcast via Any:: instead of unsafe transmute.
3692        // The stored value is Arc<RwLock<dyn Any>>. We obtain a read lock
3693        // to verify that the inner type is indeed T.
3694        let any_ref = stored.read().ok()?;
3695        // downcast_ref checks the vtable at runtime -- no unsafe needed.
3696        let _verified: &T = any_ref.downcast_ref::<T>()?;
3697        drop(any_ref);
3698        // Recover the original Arc. The thin pointer cast is sound because we
3699        // have verified the concrete type via Any::downcast_ref above.
3700        let raw = Arc::into_raw(stored.clone());
3701        Some(unsafe { Arc::from_raw(raw as *const std::sync::RwLock<T>) })
3702    }
3703    /// Add a new fragment to memory.
3704    pub fn remember(&mut self, fragment: KnowledgeFragment) {
3705        self.fragments.insert(fragment.id.clone(), fragment);
3706    }
3707    /// Process a search query against the local knowledge base.
3708    pub fn process_query(&mut self, query: &str) {
3709        let query_lower = query.to_lowercase();
3710        let mut results: Vec<(f32, String)> = self
3711            .fragments
3712            .iter()
3713            .map(|(id, frag)| {
3714                let mut score = 0.0;
3715                if frag.summary.to_lowercase().contains(&query_lower) {
3716                    score += 1.0;
3717                }
3718                if frag.source.to_lowercase().contains(&query_lower) {
3719                    score += 0.5;
3720                }
3721                (score, id.clone())
3722            })
3723            .filter(|(score, _)| *score > 0.0)
3724            .collect();
3725        // Sort by relevance score
3726        results.sort_by(|a, b| b.0.total_cmp(&a.0));
3727        self.last_query_results = results.into_iter().map(|(_, id)| id).take(5).collect();
3728    }
3729    /// Captures a snapshot of the current state for debugging and hot-reloading.
3730    pub fn snapshot(&self) -> Vec<NodeStateSnapshot> {
3731        let mut snapshots = Vec::new();
3732        // Snapshots of agentic fragments
3733        for frag in self.fragments.values() {
3734            if let Ok(val) = serde_json::to_value(frag) {
3735                snapshots.push(NodeStateSnapshot { id: 0, state: val });
3736            }
3737        }
3738        snapshots
3739    }
3740}
3741/// A read/write projection into a `State<T>` owned elsewhere.
3742#[derive(Clone)]
3743pub struct Binding<T: Clone + Send + Sync + 'static> {
3744    swap: Arc<arc_swap::ArcSwap<T>>,
3745    #[cfg(not(target_arch = "wasm32"))]
3746    tvar: Arc<stm::TVar<T>>,
3747    version: Arc<std::sync::atomic::AtomicU64>,
3748}
3749impl<T: Clone + Send + Sync + 'static> Binding<T> {
3750    /// Create a binding from a State
3751    pub fn from_state(state: &State<T>) -> Self {
3752        Self {
3753            swap: Arc::clone(&state.swap),
3754            #[cfg(not(target_arch = "wasm32"))]
3755            tvar: Arc::clone(&state.tvar),
3756            version: Arc::clone(&state.version),
3757        }
3758    }
3759    /// Get the current value
3760    pub fn get(&self) -> T {
3761        (**self.swap.load()).clone()
3762    }
3763    /// Set a new value
3764    pub fn set(&self, value: T) {
3765        self.swap.store(Arc::new(value.clone()));
3766        #[cfg(not(target_arch = "wasm32"))]
3767        {
3768            let tvar = Arc::clone(&self.tvar);
3769            let v = value.clone();
3770            stm::atomically(move |tx| tvar.write(tx, v.clone()));
3771        }
3772        self.version
3773            .fetch_add(1, std::sync::atomic::Ordering::Release);
3774    }
3775    /// Get current version
3776    pub fn version(&self) -> u64 {
3777        self.version.load(std::sync::atomic::Ordering::Acquire)
3778    }
3779}
3780#[cfg(not(target_arch = "wasm32"))]
3781pub fn transact_pair<A, B, F>(state_a: &State<A>, state_b: &State<B>, f: F)
3782where
3783    A: Clone + Send + Sync + 'static,
3784    B: Clone + Send + Sync + 'static,
3785    F: Fn(&A, &B) -> (A, B),
3786{
3787    let tvar_a = Arc::clone(&state_a.tvar);
3788    let tvar_b = Arc::clone(&state_b.tvar);
3789    let (new_a, new_b) = stm::atomically(move |tx| {
3790        let a = tvar_a.read(tx)?;
3791        let b = tvar_b.read(tx)?;
3792        let (na, nb) = f(&a, &b);
3793        tvar_a.write(tx, na.clone())?;
3794        tvar_b.write(tx, nb.clone())?;
3795        Ok((na, nb))
3796    });
3797    state_a.swap.store(Arc::new(new_a.clone()));
3798    state_b.swap.store(Arc::new(new_b.clone()));
3799    state_a
3800        .version
3801        .fetch_add(1, std::sync::atomic::Ordering::Release);
3802    state_b
3803        .version
3804        .fetch_add(1, std::sync::atomic::Ordering::Release);
3805    let subs_a = Arc::clone(&state_a.subscribers);
3806    let subs_b = Arc::clone(&state_b.subscribers);
3807    if crate::is_batching() {
3808        crate::enqueue_batch_task(Box::new(move || {
3809            {
3810                let s = subs_a.lock().unwrap_or_else(|p| p.into_inner());
3811                for cb in s.iter() {
3812                    cb(&new_a);
3813                }
3814            }
3815            {
3816                let s = subs_b.lock().unwrap_or_else(|p| p.into_inner());
3817                for cb in s.iter() {
3818                    cb(&new_b);
3819                }
3820            }
3821        }));
3822    } else {
3823        {
3824            let s = subs_a.lock().unwrap_or_else(|p| p.into_inner());
3825            for cb in s.iter() {
3826                cb(&new_a);
3827            }
3828        }
3829        {
3830            let s = subs_b.lock().unwrap_or_else(|p| p.into_inner());
3831            for cb in s.iter() {
3832                cb(&new_b);
3833            }
3834        }
3835    }
3836}
3837use std::any::TypeId;
3838use std::sync::Mutex;
3839/// Global environment storage using TypeId as keys.
3840pub(crate) static ENVIRONMENT: OnceLock<
3841    Mutex<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
3842> = OnceLock::new();
3843/// Environment key type for accessing ambient values
3844/// Implement this trait to define a new environment key.
3845pub trait EnvKey: 'static + Send + Sync {
3846    /// The type of value stored in the environment
3847    type Value: Clone + Send + Sync + 'static;
3848    /// Get a default value for this key
3849    fn default_value() -> Self::Value;
3850}
3851/// Key for accessing the Yggdrasil design token tree
3852pub struct YggdrasilKey;
3853impl EnvKey for YggdrasilKey {
3854    type Value = DesignTokens;
3855    fn default_value() -> Self::Value {
3856        default_tokens()
3857    }
3858}
3859// Duplicate AssetKey removed - original definition at line 63
3860/// System appearance (Light/Dark mode)
3861#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3862pub enum Appearance {
3863    Light,
3864    Dark,
3865}
3866/// Orientation for layouts
3867#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3868pub enum Orientation {
3869    Horizontal,
3870    Vertical,
3871}
3872/// Placement configuration for placing a view within a Grid layout.
3873#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
3874pub struct GridPlacement {
3875    /// 0-based column index. Negative values count from the end of columns.
3876    pub column: i32,
3877    /// Number of columns the view spans (default is 1).
3878    pub column_span: u32,
3879    /// 0-based row index. Negative values count from the end of rows.
3880    pub row: i32,
3881    /// Number of rows the view spans (default is 1).
3882    pub row_span: u32,
3883}
3884/// Cross-axis alignment for layout containers.
3885#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3886pub enum Alignment {
3887    #[default]
3888    Center,
3889    Leading,
3890    Trailing,
3891    Top,
3892    Bottom,
3893}
3894/// Main-axis distribution for linear layout containers.
3895#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
3896pub enum Distribution {
3897    #[default]
3898    Fill,
3899    Center,
3900    Leading,
3901    Trailing,
3902    SpaceBetween,
3903    SpaceAround,
3904    SpaceEvenly,
3905}
3906/// A color represented by RGBA components in the [0.0, 1.0] range.
3907#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
3908pub struct Color {
3909    pub r: f32,
3910    pub g: f32,
3911    pub b: f32,
3912    pub a: f32,
3913}
3914impl Color {
3915    pub const BLACK: Color = Color {
3916        r: 0.0,
3917        g: 0.0,
3918        b: 0.0,
3919        a: 1.0,
3920    };
3921    pub const WHITE: Color = Color {
3922        r: 1.0,
3923        g: 1.0,
3924        b: 1.0,
3925        a: 1.0,
3926    };
3927    pub const TRANSPARENT: Color = Color {
3928        r: 0.0,
3929        g: 0.0,
3930        b: 0.0,
3931        a: 0.0,
3932    };
3933    pub const RED: Color = Color {
3934        r: 1.0,
3935        g: 0.0,
3936        b: 0.0,
3937        a: 1.0,
3938    };
3939    pub const GREEN: Color = Color {
3940        r: 0.0,
3941        g: 1.0,
3942        b: 0.0,
3943        a: 1.0,
3944    };
3945    pub const BLUE: Color = Color {
3946        r: 0.0,
3947        g: 0.0,
3948        b: 1.0,
3949        a: 1.0,
3950    };
3951    pub const VIKING_GOLD: Color = Color {
3952        r: 1.0,
3953        g: 0.84,
3954        b: 0.0,
3955        a: 1.0,
3956    };
3957    pub const MAGENTA_LIQUID: Color = Color {
3958        r: 1.0,
3959        g: 0.0,
3960        b: 1.0,
3961        a: 1.0,
3962    };
3963    pub const TACTICAL_OBSIDIAN: Color = Color {
3964        r: 0.05,
3965        g: 0.05,
3966        b: 0.07,
3967        a: 1.0,
3968    };
3969    /// Calculate the relative luminance of the color as defined by WCAG 2.x
3970    pub fn relative_luminance(&self) -> f32 {
3971        fn res(c: f32) -> f32 {
3972            if c <= 0.03928 {
3973                c / 12.92
3974            } else {
3975                ((c + 0.055) / 1.055).powf(2.4)
3976            }
3977        }
3978        0.2126 * res(self.r) + 0.7152 * res(self.g) + 0.0722 * res(self.b)
3979    }
3980    /// Calculate the contrast ratio between this color and another color
3981    pub fn contrast_ratio(&self, other: &Color) -> f32 {
3982        let l1 = self.relative_luminance();
3983        let l2 = other.relative_luminance();
3984        if l1 > l2 {
3985            (l1 + 0.05) / (l2 + 0.05)
3986        } else {
3987            (l2 + 0.05) / (l1 + 0.05)
3988        }
3989    }
3990    pub const CYAN: Color = Color {
3991        r: 0.0,
3992        g: 1.0,
3993        b: 1.0,
3994        a: 1.0,
3995    };
3996    pub const YELLOW: Color = Color {
3997        r: 1.0,
3998        g: 1.0,
3999        b: 0.0,
4000        a: 1.0,
4001    };
4002    pub const MAGENTA: Color = Color {
4003        r: 1.0,
4004        g: 0.0,
4005        b: 1.0,
4006        a: 1.0,
4007    };
4008    pub const GRAY: Color = Color {
4009        r: 0.5,
4010        g: 0.5,
4011        b: 0.5,
4012        a: 1.0,
4013    };
4014
4015    /// Parse a HEX color string (e.g., "#FF6B35" or "FF6B35") into a Color.
4016    /// Returns None if the string is not a valid 6-digit HEX color.
4017    pub fn from_hex(hex: &str) -> Option<Self> {
4018        let hex = hex.strip_prefix('#').unwrap_or(hex);
4019        if hex.len() != 6 {
4020            return None;
4021        }
4022        let r = u8::from_str_radix(&hex[0..2], 16).ok()? as f32 / 255.0;
4023        let g = u8::from_str_radix(&hex[2..4], 16).ok()? as f32 / 255.0;
4024        let b = u8::from_str_radix(&hex[4..6], 16).ok()? as f32 / 255.0;
4025        Some(Color { r, g, b, a: 1.0 })
4026    }
4027    /// Create a new color from RGBA components.
4028    pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
4029        Self { r, g, b, a }
4030    }
4031    /// Convert the color to a [r, g, b, a] array.
4032    pub fn as_array(&self) -> [f32; 4] {
4033        [self.r, self.g, self.b, self.a]
4034    }
4035
4036    /// Return a new color with lightness increased by `amount`.
4037    ///
4038    /// Adds `amount` to each RGB channel and clamps to [0.0, 1.0].
4039    /// This is a simple sRGB lightness adjustment, not perceptually uniform.
4040    /// For perceptually uniform adjustments, use OKLCH via cvkg-themes.
4041    pub fn lighten(&self, amount: f32) -> Self {
4042        Self {
4043            r: (self.r + amount).clamp(0.0, 1.0),
4044            g: (self.g + amount).clamp(0.0, 1.0),
4045            b: (self.b + amount).clamp(0.0, 1.0),
4046            a: self.a,
4047        }
4048    }
4049
4050    /// Return a new color with lightness decreased by `amount`.
4051    pub fn darken(&self, amount: f32) -> Self {
4052        Self {
4053            r: (self.r - amount).clamp(0.0, 1.0),
4054            g: (self.g - amount).clamp(0.0, 1.0),
4055            b: (self.b - amount).clamp(0.0, 1.0),
4056            a: self.a,
4057        }
4058    }
4059}
4060impl View for Color {
4061    type Body = Never;
4062    fn body(self) -> Self::Body {
4063        // SAFETY: `Never` is uninhabitable. Color is a primitive view that fills a
4064        // rectangle directly in `render()` and never exposes a composable body.
4065        unreachable!()
4066    }
4067    fn render(&self, renderer: &mut dyn Renderer, rect: Rect) {
4068        renderer.fill_rect(rect, self.as_array());
4069    }
4070}
4071/// Key for accessing the current system appearance
4072pub struct AppearanceKey;
4073impl EnvKey for AppearanceKey {
4074    type Value = Appearance;
4075    fn default_value() -> Self::Value {
4076        Appearance::Dark // Default to Dark (Ginnungagap) for Berserker aesthetic
4077    }
4078}
4079
4080/// Key for accessing the current text direction
4081pub struct DirectionKey;
4082impl EnvKey for DirectionKey {
4083    type Value = Direction;
4084    fn default_value() -> Self::Value {
4085        Direction::LTR
4086    }
4087}
4088
4089/// StyleResolver provides high-level access to themed values from the environment.
4090pub struct StyleResolver;
4091impl StyleResolver {
4092    /// Resolve a color from the current environment
4093    pub fn color(key: &str) -> String {
4094        let tokens = Environment::<YggdrasilKey>::new().get();
4095        let appearance = Environment::<AppearanceKey>::new().get();
4096        let is_dark = appearance == Appearance::Dark;
4097        tokens
4098            .get_color(key, is_dark)
4099            .unwrap_or_else(|| "#FF00FF".to_string()) // Default to MuspelMagenta on failure
4100    }
4101    /// Resolve a generic token value
4102    pub fn get<T: FromStr>(category: &str, key: &str) -> Option<T> {
4103        let tokens = Environment::<YggdrasilKey>::new().get();
4104        let appearance = Environment::<AppearanceKey>::new().get();
4105        let is_dark = appearance == Appearance::Dark;
4106        tokens.get(category, key, is_dark)
4107    }
4108    /// Resolve a color from the current environment as a [f32; 4] RGBA array.
4109    /// Returns the color value for the current appearance (light/dark).
4110    /// Falls back to magenta (#FF00FF) if the key is not found.
4111    pub fn color_array(key: &str) -> [f32; 4] {
4112        let hex = Self::color(key);
4113        parse_hex_color(&hex)
4114    }
4115}
4116
4117/// Parse a hex color string (#RRGGBB or #RRGGBBAA) into [f32; 4] RGBA.
4118fn parse_hex_color(hex: &str) -> [f32; 4] {
4119    let hex = hex.trim_start_matches('#');
4120    if hex.len() >= 6 {
4121        let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255) as f32 / 255.0;
4122        let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f32 / 255.0;
4123        let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255) as f32 / 255.0;
4124        let a = if hex.len() >= 8 {
4125            u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f32 / 255.0
4126        } else {
4127            1.0
4128        };
4129        [r, g, b, a]
4130    } else {
4131        [1.0, 0.0, 1.0, 1.0] // Magenta fallback
4132    }
4133}
4134
4135/// The authoritative Cyberpunk Viking default tokens
4136pub fn default_tokens() -> DesignTokens {
4137    let mut tokens = DesignTokens::new();
4138    // Core Norse Colorways
4139    tokens.color.insert(
4140        "background".to_string(),
4141        TokenValue::Adaptive {
4142            light: "#FFFFFF".to_string(), // Light mode: white background
4143            dark: "#000000".to_string(),  // Dark mode: Ginnungagap (The Void)
4144        },
4145    );
4146    tokens.color.insert(
4147        "primary".to_string(),
4148        TokenValue::Adaptive {
4149            light: "#007B8A".to_string(), // Light mode: muted cyan
4150            dark: "#00FFFF".to_string(),  // Dark mode: NiflCyan (Aesir Primary)
4151        },
4152    );
4153    tokens.color.insert(
4154        "secondary".to_string(),
4155        TokenValue::Adaptive {
4156            light: "#8A008A".to_string(), // Light mode: muted magenta
4157            dark: "#FF00FF".to_string(),  // Dark mode: MuspelMagenta (Berserker Secondary)
4158        },
4159    );
4160    tokens.color.insert(
4161        "surface".to_string(),
4162        TokenValue::Adaptive {
4163            light: "#FFFFFF".to_string(),
4164            dark: "#121212".to_string(),
4165        },
4166    );
4167    tokens.color.insert(
4168        "text".to_string(),
4169        TokenValue::Adaptive {
4170            light: "#000000".to_string(),
4171            dark: "#FFFFFF".to_string(),
4172        },
4173    );
4174    // Semantic component tokens
4175    tokens.color.insert(
4176        "surface_elevated".to_string(),
4177        TokenValue::Adaptive {
4178            light: "#FFFFFF".to_string(),
4179            dark: "#1A1A24".to_string(),
4180        },
4181    );
4182    tokens.color.insert(
4183        "surface_overlay".to_string(),
4184        TokenValue::Adaptive {
4185            light: "#FFFFFF".to_string(),
4186            dark: "#1E1E2E".to_string(),
4187        },
4188    );
4189    tokens.color.insert(
4190        "border".to_string(),
4191        TokenValue::Adaptive {
4192            light: "#D0D0D8".to_string(),
4193            dark: "#2A2A3A".to_string(),
4194        },
4195    );
4196    tokens.color.insert(
4197        "border_strong".to_string(),
4198        TokenValue::Adaptive {
4199            light: "#A0A0B0".to_string(),
4200            dark: "#3A3A50".to_string(),
4201        },
4202    );
4203    tokens.color.insert(
4204        "text_muted".to_string(),
4205        TokenValue::Adaptive {
4206            light: "#606070".to_string(),
4207            dark: "#8080A0".to_string(),
4208        },
4209    );
4210    tokens.color.insert(
4211        "text_dim".to_string(),
4212        TokenValue::Adaptive {
4213            light: "#9090A0".to_string(),
4214            dark: "#505070".to_string(),
4215        },
4216    );
4217    tokens.color.insert(
4218        "accent".to_string(),
4219        TokenValue::Adaptive {
4220            light: "#007B8A".to_string(), // Light mode: muted cyan
4221            dark: "#00FFFF".to_string(),  // Dark mode: NiflCyan
4222        },
4223    );
4224    tokens.color.insert(
4225        "accent_hover".to_string(),
4226        TokenValue::Adaptive {
4227            light: "#00A0B0".to_string(), // Light mode: lighter muted cyan
4228            dark: "#33FFFF".to_string(),  // Dark mode: brighter cyan
4229        },
4230    );
4231    tokens.color.insert(
4232        "success".to_string(),
4233        TokenValue::Single {
4234            value: "#00E676".to_string(),
4235        },
4236    );
4237    tokens.color.insert(
4238        "warning".to_string(),
4239        TokenValue::Single {
4240            value: "#FFB300".to_string(),
4241        },
4242    );
4243    tokens.color.insert(
4244        "error".to_string(),
4245        TokenValue::Single {
4246            value: "#FF5252".to_string(),
4247        },
4248    );
4249    tokens.color.insert(
4250        "info".to_string(),
4251        TokenValue::Single {
4252            value: "#448AFF".to_string(),
4253        },
4254    );
4255    tokens.color.insert(
4256        "hover".to_string(),
4257        TokenValue::Adaptive {
4258            light: "#F0F0F5".to_string(),
4259            dark: "#252535".to_string(),
4260        },
4261    );
4262    tokens.color.insert(
4263        "active".to_string(),
4264        TokenValue::Adaptive {
4265            light: "#E0E0EB".to_string(),
4266            dark: "#303045".to_string(),
4267        },
4268    );
4269    tokens.color.insert(
4270        "disabled".to_string(),
4271        TokenValue::Adaptive {
4272            light: "#E8E8F0".to_string(),
4273            dark: "#1A1A28".to_string(),
4274        },
4275    );
4276    tokens.color.insert(
4277        "disabled_text".to_string(),
4278        TokenValue::Adaptive {
4279            light: "#B0B0C0".to_string(),
4280            dark: "#404060".to_string(),
4281        },
4282    );
4283    tokens.color.insert(
4284        "focus_ring".to_string(),
4285        TokenValue::Single {
4286            value: "#00FFFF".to_string(),
4287        },
4288    );
4289    tokens.color.insert(
4290        "shadow".to_string(),
4291        TokenValue::Adaptive {
4292            light: "#00000020".to_string(),
4293            dark: "#00000060".to_string(),
4294        },
4295    );
4296    tokens.color.insert(
4297        "code_bg".to_string(),
4298        TokenValue::Adaptive {
4299            light: "#F5F5FA".to_string(),
4300            dark: "#0D0D18".to_string(),
4301        },
4302    );
4303    // Bifrost (Glassmorphism) - Frosted Style
4304    tokens.bifrost.insert(
4305        "blur".to_string(),
4306        TokenValue::Single {
4307            value: "25.0".to_string(),
4308        },
4309    );
4310    tokens.bifrost.insert(
4311        "saturation".to_string(),
4312        TokenValue::Single {
4313            value: "1.2".to_string(),
4314        },
4315    );
4316    tokens.bifrost.insert(
4317        "opacity".to_string(),
4318        TokenValue::Single {
4319            value: "0.65".to_string(),
4320        },
4321    );
4322    // Gungnir (Neon Glow)
4323    tokens.gungnir.insert(
4324        "intensity".to_string(),
4325        TokenValue::Single {
4326            value: "1.0".to_string(),
4327        },
4328    );
4329    tokens.gungnir.insert(
4330        "radius".to_string(),
4331        TokenValue::Single {
4332            value: "15.0".to_string(),
4333        },
4334    );
4335    // Mjolnir (Sharp Geometry)
4336    tokens.mjolnir.insert(
4337        "clip_angle".to_string(),
4338        TokenValue::Single {
4339            value: "12.0".to_string(),
4340        },
4341    );
4342    tokens.mjolnir.insert(
4343        "border_width".to_string(),
4344        TokenValue::Single {
4345            value: "2.0".to_string(),
4346        },
4347    );
4348    // Sleipnir (Spring Animation)
4349    tokens.anim.insert(
4350        "stiffness".to_string(),
4351        TokenValue::Single {
4352            value: "170.0".to_string(),
4353        },
4354    );
4355    tokens.anim.insert(
4356        "damping".to_string(),
4357        TokenValue::Single {
4358            value: "26.0".to_string(),
4359        },
4360    );
4361    tokens.anim.insert(
4362        "mass".to_string(),
4363        TokenValue::Single {
4364            value: "1.0".to_string(),
4365        },
4366    );
4367    // Accessibility
4368    tokens.accessibility.insert(
4369        "reduce_motion".to_string(),
4370        TokenValue::Single {
4371            value: "false".to_string(),
4372        },
4373    );
4374    tokens
4375}
4376/// Environment wrapper for accessing ambient values
4377pub struct Environment<K: EnvKey> {
4378    _marker: std::marker::PhantomData<K>,
4379}
4380impl<K: EnvKey> Default for Environment<K> {
4381    fn default() -> Self {
4382        Self::new()
4383    }
4384}
4385impl<K: EnvKey> Environment<K> {
4386    /// Create a new Environment
4387    pub fn new() -> Self {
4388        Self {
4389            _marker: std::marker::PhantomData,
4390        }
4391    }
4392    /// Get the current value from the environment
4393    pub fn get(&self) -> K::Value {
4394        if let Some(env_store) = ENVIRONMENT.get() {
4395            let env_lock = env_store.lock().unwrap_or_else(|p| p.into_inner());
4396            if let Some(val) = env_lock.get(&std::any::TypeId::of::<K>()) {
4397                if let Some(typed_val) = val.downcast_ref::<K::Value>() {
4398                    return typed_val.clone();
4399                } else {
4400                    log::warn!(
4401                        "Environment: Downcast failed for key type {:?}",
4402                        std::any::type_name::<K>()
4403                    );
4404                }
4405            } else {
4406                // Lowered to trace to avoid terminal logging overhead under standard debug runs
4407                log::trace!(
4408                    "Environment: Key not found: {:?}. Returning default.",
4409                    std::any::type_name::<K>()
4410                );
4411            }
4412        } else {
4413            // Lowered to trace to avoid terminal logging overhead under standard debug runs
4414            log::trace!(
4415                "Environment: Store not initialized. Key: {:?}. Returning default.",
4416                std::any::type_name::<K>()
4417            );
4418        }
4419        K::default_value()
4420    }
4421}
4422/// Ambient environment management
4423pub mod env {
4424    /// Insert a value into the environment
4425    pub fn insert<K: super::EnvKey>(value: K::Value) {
4426        let store = super::ENVIRONMENT
4427            .get_or_init(|| std::sync::Mutex::new(std::collections::HashMap::new()));
4428        let mut env_map = store.lock().unwrap_or_else(|p| p.into_inner());
4429        env_map.insert(std::any::TypeId::of::<K>(), Box::new(value));
4430    }
4431    /// Remove a value from the environment.
4432    pub fn remove<K: super::EnvKey>() {
4433        if let Some(store) = super::ENVIRONMENT.get() {
4434            let mut env_map = store.lock().unwrap_or_else(|p| p.into_inner());
4435            env_map.remove(&std::any::TypeId::of::<K>());
4436        }
4437    }
4438}
4439/// Geometry modifiers
4440/// Size of the view in logical pixels
4441#[derive(Debug, Clone, Copy, PartialEq)]
4442pub struct Size {
4443    pub width: f32,
4444    pub height: f32,
4445}
4446
4447impl Size {
4448    pub const ZERO: Self = Self {
4449        width: 0.0,
4450        height: 0.0,
4451    };
4452
4453    pub fn new(width: f32, height: f32) -> Self {
4454        Self { width, height }
4455    }
4456}
4457
4458/// Insets for padding
4459#[derive(Debug, Clone, Copy, PartialEq)]
4460pub struct EdgeInsets {
4461    pub top: f32,
4462    pub leading: f32,
4463    pub bottom: f32,
4464    pub trailing: f32,
4465}
4466
4467impl EdgeInsets {
4468    /// Equal insets on all edges
4469    pub fn all(value: f32) -> Self {
4470        Self {
4471            top: value,
4472            leading: value,
4473            bottom: value,
4474            trailing: value,
4475        }
4476    }
4477
4478    /// Vertical insets (top and bottom)
4479    pub fn vertical(value: f32) -> Self {
4480        Self {
4481            top: value,
4482            leading: 0.0,
4483            bottom: value,
4484            trailing: 0.0,
4485        }
4486    }
4487
4488    /// Horizontal insets (leading and trailing)
4489    pub fn horizontal(value: f32) -> Self {
4490        Self {
4491            top: 0.0,
4492            leading: value,
4493            bottom: 0.0,
4494            trailing: value,
4495        }
4496    }
4497}
4498
4499/// Modifier to set the size and alignment constraints of a view.
4500/// This determines the proposal size passed to the child and how the child is aligned
4501/// within the layout rect allocated to the frame.
4502#[derive(Debug, Clone, Copy, PartialEq)]
4503pub struct FrameModifier {
4504    /// Exact width to assign to the child view.
4505    pub width: Option<f32>,
4506    /// Exact height to assign to the child view.
4507    pub height: Option<f32>,
4508    /// Minimum width constraint for the view.
4509    pub min_width: Option<f32>,
4510    /// Maximum width constraint for the view.
4511    pub max_width: Option<f32>,
4512    /// Minimum height constraint for the view.
4513    pub min_height: Option<f32>,
4514    /// Maximum height constraint for the view.
4515    pub max_height: Option<f32>,
4516    /// The alignment strategy for positioning the child view within the frame.
4517    pub alignment: Alignment,
4518}
4519
4520impl Default for FrameModifier {
4521    /// Returns the default frame configuration which has no constraints and center alignment.
4522    fn default() -> Self {
4523        Self::new()
4524    }
4525}
4526
4527impl FrameModifier {
4528    /// Creates a new FrameModifier with all dimensions unspecified and center alignment.
4529    pub fn new() -> Self {
4530        Self {
4531            width: None,
4532            height: None,
4533            min_width: None,
4534            max_width: None,
4535            min_height: None,
4536            max_height: None,
4537            alignment: Alignment::Center,
4538        }
4539    }
4540
4541    /// Sets the fixed width of the frame.
4542    pub fn width(mut self, width: f32) -> Self {
4543        self.width = Some(width);
4544        self
4545    }
4546
4547    /// Sets the fixed height of the frame.
4548    pub fn height(mut self, height: f32) -> Self {
4549        self.height = Some(height);
4550        self
4551    }
4552
4553    /// Sets both the fixed width and height of the frame.
4554    pub fn size(mut self, width: f32, height: f32) -> Self {
4555        self.width = Some(width);
4556        self.height = Some(height);
4557        self
4558    }
4559
4560    /// Sets the minimum width constraint.
4561    pub fn min_width(mut self, min_width: f32) -> Self {
4562        self.min_width = Some(min_width);
4563        self
4564    }
4565
4566    /// Sets the maximum width constraint.
4567    pub fn max_width(mut self, max_width: f32) -> Self {
4568        self.max_width = Some(max_width);
4569        self
4570    }
4571
4572    /// Sets the minimum height constraint.
4573    pub fn min_height(mut self, min_height: f32) -> Self {
4574        self.min_height = Some(min_height);
4575        self
4576    }
4577
4578    /// Sets the maximum height constraint.
4579    pub fn max_height(mut self, max_height: f32) -> Self {
4580        self.max_height = Some(max_height);
4581        self
4582    }
4583
4584    /// Sets the alignment strategy for the child within the frame's layout bounds.
4585    pub fn alignment(mut self, alignment: Alignment) -> Self {
4586        self.alignment = alignment;
4587        self
4588    }
4589}
4590
4591impl ViewModifier for FrameModifier {
4592    /// Wraps the child view in a ModifiedView using this frame modifier.
4593    fn modify<V: View>(self, content: V) -> impl View {
4594        ModifiedView::new(content, self)
4595    }
4596
4597    /// Transforms the layout size proposal offered to the child to comply with frame constraints.
4598    fn transform_proposal(&self, proposal: SizeProposal) -> SizeProposal {
4599        let w = if let Some(width) = self.width {
4600            Some(width)
4601        } else {
4602            proposal.width.map(|pw| {
4603                pw.clamp(
4604                    self.min_width.unwrap_or(0.0),
4605                    self.max_width.unwrap_or(f32::INFINITY),
4606                )
4607            })
4608        };
4609        let h = if let Some(height) = self.height {
4610            Some(height)
4611        } else {
4612            proposal.height.map(|ph| {
4613                ph.clamp(
4614                    self.min_height.unwrap_or(0.0),
4615                    self.max_height.unwrap_or(f32::INFINITY),
4616                )
4617            })
4618        };
4619        SizeProposal {
4620            width: w,
4621            height: h,
4622        }
4623    }
4624
4625    /// Constraints and transforms the child's resulting size to fit the frame's bounds.
4626    fn transform_size(&self, child_size: Size) -> Size {
4627        let w = if let Some(width) = self.width {
4628            width
4629        } else {
4630            child_size.width.clamp(
4631                self.min_width.unwrap_or(0.0),
4632                self.max_width.unwrap_or(f32::INFINITY),
4633            )
4634        };
4635        let h = if let Some(height) = self.height {
4636            height
4637        } else {
4638            child_size.height.clamp(
4639                self.min_height.unwrap_or(0.0),
4640                self.max_height.unwrap_or(f32::INFINITY),
4641            )
4642        };
4643        Size {
4644            width: w,
4645            height: h,
4646        }
4647    }
4648
4649    /// Renders the frame's child view aligned within the layout rect.
4650    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4651        self.render(renderer, rect);
4652        let child_proposal =
4653            self.transform_proposal(SizeProposal::new(Some(rect.width), Some(rect.height)));
4654        let child_size = view.intrinsic_size(renderer, child_proposal);
4655
4656        let mut child_x = rect.x;
4657        let mut child_y = rect.y;
4658
4659        match self.alignment {
4660            Alignment::Leading => {
4661                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4662            }
4663            Alignment::Trailing => {
4664                child_x = rect.x + rect.width - child_size.width;
4665                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4666            }
4667            Alignment::Top => {
4668                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4669            }
4670            Alignment::Bottom => {
4671                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4672                child_y = rect.y + rect.height - child_size.height;
4673            }
4674            Alignment::Center => {
4675                child_x = rect.x + (rect.width - child_size.width) / 2.0;
4676                child_y = rect.y + (rect.height - child_size.height) / 2.0;
4677            }
4678        }
4679
4680        let child_rect = Rect {
4681            x: child_x,
4682            y: child_y,
4683            width: child_size.width,
4684            height: child_size.height,
4685        };
4686
4687        view.render(renderer, child_rect);
4688        self.post_render(renderer, rect);
4689    }
4690}
4691
4692/// Modifier to set the flex weight of a view
4693#[derive(Debug, Clone, Copy, PartialEq)]
4694pub struct FlexModifier {
4695    pub weight: f32,
4696}
4697
4698impl ViewModifier for FlexModifier {
4699    fn modify<V: View>(self, content: V) -> impl View {
4700        ModifiedView::new(content, self)
4701    }
4702
4703    fn child_flex_weight<V: View>(&self, _view: &V) -> f32 {
4704        self.weight
4705    }
4706}
4707
4708/// Modifier that specifies the column and row placement of a view inside a Grid layout.
4709#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4710pub struct GridPlacementModifier {
4711    /// The grid placement settings containing column/row indexes and spans.
4712    pub placement: GridPlacement,
4713}
4714
4715impl ViewModifier for GridPlacementModifier {
4716    /// Wraps the child view in a ModifiedView using this modifier.
4717    fn modify<V: View>(self, content: V) -> impl View {
4718        ModifiedView::new(content, self)
4719    }
4720
4721    /// Exposes the grid placement metadata to parent layout engines.
4722    fn get_grid_placement(&self) -> Option<GridPlacement> {
4723        Some(self.placement)
4724    }
4725}
4726
4727/// Modifier to render a popover, tooltip, or menu view overlaying an anchored view.
4728/// It supports alignment positioning and outside-click dismissal.
4729#[derive(Clone)]
4730pub struct OverlayModifier {
4731    /// The overlay content view.
4732    pub overlay: AnyView,
4733    /// Where the overlay is aligned relative to the anchored view.
4734    pub alignment: Alignment,
4735    /// Additional offset in logical pixels.
4736    pub offset: [f32; 2],
4737    /// Optional dismissal callback triggered by click-outside events.
4738    pub on_dismiss: Option<Arc<dyn Fn() + Send + Sync>>,
4739}
4740
4741impl ViewModifier for OverlayModifier {
4742    /// Wraps the child view in a ModifiedView using this overlay modifier.
4743    fn modify<V: View>(self, content: V) -> impl View {
4744        ModifiedView::new(content, self)
4745    }
4746
4747    /// Renders the overlay content positioned above the child view.
4748    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4749        // 1. Render primary anchored view
4750        view.render(renderer, rect);
4751
4752        // 2. Measure overlay content
4753        let overlay_size = self
4754            .overlay
4755            .intrinsic_size(renderer, SizeProposal::unspecified());
4756
4757        // 3. Align overlay rect relative to anchored rect
4758        let mut overlay_x;
4759        let mut overlay_y;
4760
4761        match self.alignment {
4762            Alignment::Leading => {
4763                overlay_x = rect.x - overlay_size.width;
4764                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4765            }
4766            Alignment::Trailing => {
4767                overlay_x = rect.x + rect.width;
4768                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4769            }
4770            Alignment::Top => {
4771                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4772                overlay_y = rect.y - overlay_size.height;
4773            }
4774            Alignment::Bottom => {
4775                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4776                overlay_y = rect.y + rect.height;
4777            }
4778            Alignment::Center => {
4779                overlay_x = rect.x + (rect.width - overlay_size.width) / 2.0;
4780                overlay_y = rect.y + (rect.height - overlay_size.height) / 2.0;
4781            }
4782        }
4783
4784        overlay_x += self.offset[0];
4785        overlay_y += self.offset[1];
4786
4787        let overlay_rect = Rect {
4788            x: overlay_x,
4789            y: overlay_y,
4790            width: overlay_size.width,
4791            height: overlay_size.height,
4792        };
4793
4794        // 4. Handle click-outside dismissal
4795        if let Some(on_dismiss) = &self.on_dismiss {
4796            let dismiss = on_dismiss.clone();
4797            renderer.register_handler(
4798                "pointerdown",
4799                Arc::new(move |event| {
4800                    if let Event::PointerDown { x, y, .. } = event {
4801                        let click_inside = x >= overlay_rect.x
4802                            && x <= overlay_rect.x + overlay_rect.width
4803                            && y >= overlay_rect.y
4804                            && y <= overlay_rect.y + overlay_rect.height;
4805                        if !click_inside {
4806                            dismiss();
4807                        }
4808                    }
4809                }),
4810            );
4811        }
4812
4813        // 5. Render overlay view
4814        self.overlay.render(renderer, overlay_rect);
4815    }
4816}
4817
4818/// Modifier to offset a view
4819#[derive(Debug, Clone, Copy, PartialEq)]
4820pub struct OffsetModifier {
4821    pub x: f32,
4822    pub y: f32,
4823}
4824
4825impl OffsetModifier {
4826    pub fn new(x: f32, y: f32) -> Self {
4827        Self { x, y }
4828    }
4829}
4830
4831impl ViewModifier for OffsetModifier {
4832    fn modify<V: View>(self, content: V) -> impl View {
4833        ModifiedView::new(content, self)
4834    }
4835}
4836
4837/// Modifier to set the z-index of a view
4838#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4839pub struct ZIndexModifier {
4840    pub z_index: i32,
4841}
4842
4843impl ZIndexModifier {
4844    pub fn new(z_index: i32) -> Self {
4845        Self { z_index }
4846    }
4847}
4848
4849impl ViewModifier for ZIndexModifier {
4850    fn modify<V: View>(self, content: V) -> impl View {
4851        ModifiedView::new(content, self)
4852    }
4853}
4854
4855/// Layout constraints for views
4856#[derive(Debug, Clone, Copy, PartialEq, Default)]
4857pub struct LayoutConstraints {
4858    pub min_width: Option<f32>,
4859    pub max_width: Option<f32>,
4860    pub min_height: Option<f32>,
4861    pub max_height: Option<f32>,
4862}
4863
4864/// Modifier to set layout constraints
4865#[derive(Debug, Clone, Copy, PartialEq)]
4866pub struct LayoutModifier {
4867    pub constraints: LayoutConstraints,
4868}
4869
4870impl LayoutModifier {
4871    pub fn new(constraints: LayoutConstraints) -> Self {
4872        Self { constraints }
4873    }
4874}
4875
4876impl ViewModifier for LayoutModifier {
4877    fn modify<V: View>(self, content: V) -> impl View {
4878        ModifiedView::new(content, self)
4879    }
4880}
4881
4882/// Modifier to handle platform safe areas
4883#[derive(Debug, Clone, Copy, PartialEq)]
4884pub struct SafeAreaModifier {
4885    pub ignores: bool,
4886}
4887
4888impl ViewModifier for SafeAreaModifier {
4889    fn modify<V: View>(self, content: V) -> impl View {
4890        ModifiedView::new(content, self)
4891    }
4892}
4893
4894/// Modifier to add elevation (shadow) to a view
4895#[derive(Debug, Clone, Copy, PartialEq)]
4896pub struct ElevationModifier {
4897    pub level: f32,
4898}
4899
4900impl ViewModifier for ElevationModifier {
4901    fn modify<V: View>(self, content: V) -> impl View {
4902        ModifiedView::new(content, self)
4903    }
4904
4905    fn render_view<V: View>(&self, view: &V, renderer: &mut dyn Renderer, rect: Rect) {
4906        if self.level > 0.0 {
4907            let radius = self.level * 2.0;
4908            let offset_y = self.level * 0.5;
4909            let shadow_color = [0.0, 0.0, 0.0, 0.3];
4910            renderer.push_shadow(radius, shadow_color, [0.0, offset_y]);
4911            view.render(renderer, rect);
4912            renderer.pop_shadow();
4913        } else {
4914            view.render(renderer, rect);
4915        }
4916    }
4917}
4918
4919/// Position modifier — offsets a view from its layout position.
4920/// Enables absolute-like positioning within a container.
4921#[derive(Clone)]
4922pub struct PositionModifier {
4923    pub x: f32,
4924    pub y: f32,
4925}
4926
4927impl ViewModifier for PositionModifier {
4928    fn modify<V: View>(self, content: V) -> impl View {
4929        ModifiedView::new(content, self)
4930    }
4931
4932    fn transform_rect(&self, rect: Rect) -> Rect {
4933        Rect {
4934            x: rect.x + self.x,
4935            y: rect.y + self.y,
4936            width: rect.width,
4937            height: rect.height,
4938        }
4939    }
4940}
4941
4942// Layout subsystem
4943pub mod layout {
4944    use super::*;
4945
4946    /// Key used to identify a cached layout entry.
4947    /// Combines a view hash with a generation counter for cache invalidation.
4948    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4949    pub struct LayoutKey {
4950        pub view_hash: u64,
4951        pub generation: u64,
4952    }
4953
4954    // Layout pass scratch space
4955    pub struct LayoutCache {
4956        pub safe_area: SafeArea,
4957        pub delta_time: f32,
4958        /// Device scale factor for HiDPI / retina snapping. Defaults to 1.0.
4959        pub scale_factor: f32,
4960        /// The visible viewport bounds in logical pixels.
4961        /// If Some, layout execution can cull offscreen subtrees.
4962        pub viewport: Option<Rect>,
4963        /// Time budget for the layout pass. Defaults to 4.0ms.
4964        pub layout_time_budget: std::time::Duration,
4965        /// Start of the layout pass, captured at the beginning of the frame/layout run.
4966        pub layout_start_time: Option<std::time::Instant>,
4967        size_cache: HashMap<(u64, u32, u32), Size>, // (ViewHash, ProposalW, ProposalH)
4968        /// Map tracking child-to-parent view hash relationships for bottom-up invalidation.
4969        pub parent_map: HashMap<u64, u64>,
4970        /// Monotonically increasing generation counter for cache invalidation.
4971        /// When a view tree changes, bumping the generation causes stale entries
4972        /// to be treated as invalid without eagerly clearing the entire cache.
4973        generation: u64,
4974        /// Opaque pointer to the active layout engine (e.g. Taffy)
4975        pub engine: Option<Box<dyn std::any::Any + Send + Sync>>,
4976        /// Opaque pointer to the active animation orchestrator
4977        pub animators: Option<Box<dyn std::any::Any + Send + Sync>>,
4978        /// Cached previous rects for view transitions
4979        pub previous_rects: HashMap<u64, Rect>,
4980        /// Generation counter for cache eviction.
4981        /// Incremented each frame; entries not touched for N frames are evicted.
4982        pub eviction_generation: u64,
4983        /// Tracks which generation each previous_rects entry was last touched in.
4984        pub previous_rects_generation: HashMap<u64, u64>,
4985        /// Number of generations an entry can go untouched before eviction.
4986        eviction_threshold: u64,
4987    }
4988
4989    thread_local! {
4990        static LAYOUT_BUDGET_DEADLINE: std::cell::RefCell<Option<std::time::Instant>> =
4991            const { std::cell::RefCell::new(None) };
4992    }
4993
4994    impl Default for LayoutCache {
4995        fn default() -> Self {
4996            Self::new()
4997        }
4998    }
4999
5000    impl LayoutCache {
5001        pub fn new() -> Self {
5002            Self {
5003                safe_area: SafeArea::default(),
5004                delta_time: 0.016,
5005                scale_factor: 1.0,
5006                viewport: None,
5007                layout_time_budget: std::time::Duration::from_millis(4),
5008                layout_start_time: None,
5009                size_cache: HashMap::new(),
5010                parent_map: HashMap::new(),
5011                generation: 0,
5012                engine: None,
5013                animators: None,
5014                previous_rects: HashMap::new(),
5015                eviction_generation: 0,
5016                previous_rects_generation: HashMap::new(),
5017                eviction_threshold: 300, // ~5 seconds at 60fps
5018            }
5019        }
5020
5021        /// Returns the current generation counter.
5022        pub fn generation(&self) -> u64 {
5023            self.generation
5024        }
5025
5026        /// Evict entries from previous_rects that haven't been touched for N generations.
5027        pub fn evict_stale_entries(&mut self) {
5028            self.eviction_generation += 1;
5029            let threshold = self.eviction_threshold;
5030            let current_gen = self.eviction_generation;
5031            self.previous_rects.retain(|hash, _| {
5032                self.previous_rects_generation
5033                    .get(hash)
5034                    .map_or(false, |g| current_gen - *g < threshold)
5035            });
5036            self.previous_rects_generation
5037                .retain(|hash, _| self.previous_rects.contains_key(hash));
5038        }
5039
5040        /// Checks if the layout pass is currently running over its allocated time budget.
5041        pub fn is_over_budget(&self) -> bool {
5042            let deadline_red = LAYOUT_BUDGET_DEADLINE.with(|deadline| {
5043                deadline.borrow().as_ref().is_some_and(|deadline| std::time::Instant::now() >= *deadline)
5044            });
5045            if deadline_red {
5046                return true;
5047            }
5048            if let Some(start) = self.layout_start_time {
5049                start.elapsed() > self.layout_time_budget
5050            } else {
5051                false
5052            }
5053        }
5054
5055        /// Set a process-local deadline for layout cache consumers.
5056        /// When this deadline is exceeded, caches should reuse previous
5057        /// rects instead of recomputing expensive layout work.
5058        pub fn set_layout_budget_deadline(deadline: Option<std::time::Instant>) {
5059            LAYOUT_BUDGET_DEADLINE.with(|slot| {
5060                *slot.borrow_mut() = deadline;
5061            });
5062        }
5063
5064        /// Clear any process-local layout budget deadline.
5065        pub fn clear_layout_budget_deadline() {
5066            Self::set_layout_budget_deadline(None);
5067        }
5068
5069        /// Bump the generation counter, logically invalidating all cached entries
5070        /// without eagerly clearing them. Subsequent lookups with the old generation
5071        /// will miss until re-populated.
5072        pub fn invalidate(&mut self) {
5073            self.generation = self.generation.wrapping_add(1);
5074        }
5075
5076        /// Check whether a cached entry for the given key is still valid
5077        /// against the current generation.
5078        pub fn is_valid(&self, key: LayoutKey, current_gen: u64) -> bool {
5079            key.generation == current_gen && key.generation == self.generation
5080        }
5081
5082        pub fn clear(&mut self) {
5083            self.safe_area = SafeArea::default();
5084            self.viewport = None;
5085            self.layout_start_time = None;
5086            self.size_cache.clear();
5087            self.parent_map.clear();
5088        }
5089
5090        pub fn get_size(&self, view_hash: u64, proposal: SizeProposal) -> Option<Size> {
5091            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5092            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5093            self.size_cache.get(&(view_hash, pw, ph)).copied()
5094        }
5095
5096        pub fn set_size(&mut self, view_hash: u64, proposal: SizeProposal, size: Size) {
5097            let pw = (proposal.width.unwrap_or(-1.0) * 100.0) as u32;
5098            let ph = (proposal.height.unwrap_or(-1.0) * 100.0) as u32;
5099            self.size_cache.insert((view_hash, pw, ph), size);
5100        }
5101
5102        /// Register a child-to-parent layout relationship for bottom-up invalidation propagation.
5103        pub fn register_parent(&mut self, child_hash: u64, parent_hash: u64) {
5104            if child_hash != 0 && parent_hash != 0 {
5105                self.parent_map.insert(child_hash, parent_hash);
5106            }
5107        }
5108
5109        /// Remove all cached size entries for a specific view hash and propagate the invalidation
5110        /// bottom-up to all its layout ancestors to ensure consistent layout updates.
5111        pub fn invalidate_view(&mut self, view_hash: u64) {
5112            let mut to_invalidate = vec![view_hash];
5113            let mut visited = std::collections::HashSet::new();
5114            while let Some(hash) = to_invalidate.pop() {
5115                if !visited.insert(hash) {
5116                    continue;
5117                }
5118                self.size_cache.retain(|&(h, _, _), _| h != hash);
5119                if let Some(&parent) = self.parent_map.get(&hash) {
5120                    to_invalidate.push(parent);
5121                }
5122            }
5123        }
5124    }
5125
5126    /// Proposed size from parent view
5127    #[derive(Debug, Clone, Copy, PartialEq)]
5128    pub struct SizeProposal {
5129        pub width: Option<f32>,
5130        pub height: Option<f32>,
5131    }
5132
5133    impl SizeProposal {
5134        pub fn unspecified() -> Self {
5135            Self {
5136                width: None,
5137                height: None,
5138            }
5139        }
5140
5141        pub fn width(width: f32) -> Self {
5142            Self {
5143                width: Some(width),
5144                height: None,
5145            }
5146        }
5147
5148        pub fn height(height: f32) -> Self {
5149            Self {
5150                width: None,
5151                height: Some(height),
5152            }
5153        }
5154
5155        pub fn tight(width: f32, height: f32) -> Self {
5156            Self {
5157                width: Some(width),
5158                height: Some(height),
5159            }
5160        }
5161
5162        pub fn new(width: Option<f32>, height: Option<f32>) -> Self {
5163            Self { width, height }
5164        }
5165    }
5166
5167    /// A view that can participate in layout
5168    pub trait LayoutView: Send {
5169        /// Propose a size for this view given the available space
5170        fn size_that_fits(
5171            &self,
5172            proposal: SizeProposal,
5173            subviews: &[&dyn LayoutView],
5174            cache: &mut LayoutCache,
5175        ) -> Size;
5176
5177        /// Place subviews within the given bounds
5178        fn place_subviews(
5179            &self,
5180            bounds: Rect,
5181            subviews: &mut [&mut dyn LayoutView],
5182            cache: &mut LayoutCache,
5183        );
5184
5185        /// Returns the flex weight of this view (default is 0.0, which means fixed/intrinsic)
5186        fn flex_weight(&self) -> f32 {
5187            0.0
5188        }
5189
5190        /// Returns a persistent unique identifier for this view to enable Layout View Transitions.
5191        /// Return 0 (default) to disable layout animations for this node.
5192        fn view_hash(&self) -> u64 {
5193            0
5194        }
5195
5196        /// Return true when this view's layout may have changed since the last pass.
5197        ///
5198        /// The layout engine uses this to skip cache lookups for views that are
5199        /// guaranteed static (e.g., chrome elements that never change between frames).
5200        /// Default true for backward compatibility -- override false for static subtrees.
5201        ///
5202        /// When false, the engine may skip `size_that_fits` entirely and reuse the
5203        /// cached rect from `LayoutCache::previous_rects`.
5204        fn changed(&self) -> bool {
5205            true
5206        }
5207
5208        /// Return a debug representation of this layout subtree.
5209        /// The `indent` parameter controls the indentation level for nested display.
5210        fn debug_layout(&self, indent: usize) -> String {
5211            let prefix = " ".repeat(indent);
5212            format!("{}LayoutView", prefix)
5213        }
5214    }
5215    /// Edge insets for padding, margins, and safe areas
5216    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5217    pub struct EdgeInsets {
5218        pub top: f32,
5219        pub leading: f32,
5220        pub bottom: f32,
5221        pub trailing: f32,
5222    }
5223
5224    impl EdgeInsets {
5225        pub fn new(top: f32, leading: f32, bottom: f32, trailing: f32) -> Self {
5226            Self {
5227                top,
5228                leading,
5229                bottom,
5230                trailing,
5231            }
5232        }
5233
5234        pub fn all(value: f32) -> Self {
5235            Self {
5236                top: value,
5237                leading: value,
5238                bottom: value,
5239                trailing: value,
5240            }
5241        }
5242    }
5243
5244    /// SafeArea constraints provided by the platform
5245    #[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
5246    pub struct SafeArea {
5247        pub insets: EdgeInsets,
5248    }
5249
5250    /// SDF Shape definitions for Vili Interaction Paradigm hit-testing.
5251    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5252    pub enum SdfShape {
5253        Rect(Rect),
5254        RoundedRect { rect: Rect, radius: f32 },
5255        Circle { center: [f32; 2], radius: f32 },
5256    }
5257
5258    /// Rectangle in logical pixels
5259    #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
5260    pub struct Rect {
5261        pub x: f32,
5262        pub y: f32,
5263        pub width: f32,
5264        pub height: f32,
5265    }
5266
5267    impl Rect {
5268        pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
5269            Self {
5270                x,
5271                y,
5272                width,
5273                height,
5274            }
5275        }
5276
5277        pub fn inset(&self, amount: f32) -> Self {
5278            Self {
5279                x: self.x + amount,
5280                y: self.y + amount,
5281                width: (self.width - amount * 2.0).max(0.0),
5282                height: (self.height - amount * 2.0).max(0.0),
5283            }
5284        }
5285
5286        pub fn offset(&self, dx: f32, dy: f32) -> Self {
5287            Self {
5288                x: self.x + dx,
5289                y: self.y + dy,
5290                ..*self
5291            }
5292        }
5293
5294        pub fn zero() -> Self {
5295            Self {
5296                x: 0.0,
5297                y: 0.0,
5298                width: 0.0,
5299                height: 0.0,
5300            }
5301        }
5302
5303        pub fn contains(&self, x: f32, y: f32) -> bool {
5304            x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
5305        }
5306
5307        /// Determines whether this rectangle overlaps with another rectangle.
5308        ///
5309        /// # Contract
5310        /// Two rectangles overlap if their projection intervals on both the X
5311        /// and Y axes overlap. This is used for viewport intersection checks
5312        /// to determine visibility constraints during layout culling.
5313        pub fn intersects(&self, other: &Rect) -> bool {
5314            self.x < other.x + other.width
5315                && self.x + self.width > other.x
5316                && self.y < other.y + other.height
5317                && self.y + self.height > other.y
5318        }
5319
5320        pub fn size(&self) -> Size {
5321            Size {
5322                width: self.width,
5323                height: self.height,
5324            }
5325        }
5326
5327        /// Split the rect horizontally into N equal pieces
5328        pub fn split_horizontal(&self, n: usize) -> Vec<Rect> {
5329            if n == 0 {
5330                return vec![];
5331            }
5332            let item_width = self.width / n as f32;
5333            (0..n)
5334                .map(|i| Rect {
5335                    x: self.x + i as f32 * item_width,
5336                    y: self.y,
5337                    width: item_width,
5338                    height: self.height,
5339                })
5340                .collect()
5341        }
5342
5343        /// Split the rect vertically into N equal pieces
5344        pub fn split_vertical(&self, n: usize) -> Vec<Rect> {
5345            if n == 0 {
5346                return vec![];
5347            }
5348            let item_height = self.height / n as f32;
5349            (0..n)
5350                .map(|i| Rect {
5351                    x: self.x,
5352                    y: self.y + i as f32 * item_height,
5353                    width: self.width,
5354                    height: item_height,
5355                })
5356                .collect()
5357        }
5358    }
5359}
5360
5361// Size and FrameRenderer are pub items in this module; no re-export alias needed.
5362
5363pub mod agents;
5364pub mod animation;
5365pub mod gpu;
5366pub mod material;
5367pub mod runtime;
5368pub mod scene_graph;
5369pub mod sdf_shadow;
5370
5371// Re-export commonly used types
5372pub use layout::{LayoutCache, LayoutKey, LayoutView, Rect, SizeProposal};
5373pub use material::DrawMaterial;
5374pub use scene_graph::{NodeId, bifrost_registry};
5375pub use color::SemanticColors;
5376
5377// Duplicate AssetState removed - original definition at line 67
5378
5379/// AssetManager defines the interface for loading and caching external resources.
5380pub trait AssetManager: Send + Sync {
5381    /// Request an image asset. Returns the current state (Loading, Ready, or Error).
5382    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>>;
5383
5384    /// Pre-load an image into the cache.
5385    fn preload_image(&self, url: &str);
5386}
5387
5388/// The phase of a touch or gesture event in its lifecycle.
5389#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
5390pub enum TouchPhase {
5391    /// The touch/gesture has just begun.
5392    Began,
5393    /// The touch/gesture is moving.
5394    Moved,
5395    /// The touch/gesture has ended normally.
5396    Ended,
5397    /// The touch/gesture was cancelled (e.g., by the system).
5398    Cancelled,
5399}
5400
5401/// User input event types
5402#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
5403pub enum Event {
5404    PointerDown {
5405        x: f32,
5406        y: f32,
5407        button: u32,
5408        proximity_field: f32,
5409        tilt: Option<f32>,
5410        azimuth: Option<f32>,
5411        pressure: Option<f32>,
5412        barrel_rotation: Option<f32>,
5413        pointer_precision: f32,
5414    },
5415    PointerUp {
5416        x: f32,
5417        y: f32,
5418        button: u32,
5419        tilt: Option<f32>,
5420        azimuth: Option<f32>,
5421        pressure: Option<f32>,
5422        barrel_rotation: Option<f32>,
5423        pointer_precision: f32,
5424    },
5425    PointerMove {
5426        x: f32,
5427        y: f32,
5428        proximity_field: f32,
5429        tilt: Option<f32>,
5430        azimuth: Option<f32>,
5431        pressure: Option<f32>,
5432        barrel_rotation: Option<f32>,
5433        pointer_precision: f32,
5434    },
5435    PointerClick {
5436        x: f32,
5437        y: f32,
5438        button: u32,
5439        tilt: Option<f32>,
5440        azimuth: Option<f32>,
5441        pressure: Option<f32>,
5442        barrel_rotation: Option<f32>,
5443        pointer_precision: f32,
5444    },
5445    PointerEnter,
5446    PointerLeave,
5447    /// Mouse wheel / trackpad scroll event.
5448    /// `delta_x` is the horizontal scroll amount, `delta_y` is the vertical scroll amount (positive = scroll down).
5449    PointerWheel {
5450        x: f32,
5451        y: f32,
5452        delta_x: f32,
5453        delta_y: f32,
5454        pointer_precision: f32,
5455    },
5456    /// Double-click event (rapid successive clicks).
5457    PointerDoubleClick {
5458        x: f32,
5459        y: f32,
5460        button: u32,
5461        pointer_precision: f32,
5462    },
5463    /// Drag-and-drop: drag started (pointer moved while button held past threshold).
5464    DragStart {
5465        x: f32,
5466        y: f32,
5467        button: u32,
5468        pointer_precision: f32,
5469    },
5470    /// Drag-and-drop: drag in progress.
5471    DragMove {
5472        x: f32,
5473        y: f32,
5474        pointer_precision: f32,
5475    },
5476    /// Drag-and-drop: drag ended (pointer released).
5477    DragEnd {
5478        x: f32,
5479        y: f32,
5480        pointer_precision: f32,
5481    },
5482    KeyDown {
5483        key: String,
5484        modifiers: KeyModifiers,
5485    },
5486    KeyUp {
5487        key: String,
5488        modifiers: KeyModifiers,
5489    },
5490    /// Focus gained by a node.
5491    FocusIn,
5492    /// Focus lost by a node.
5493    FocusOut,
5494    /// Clipboard copy event.
5495    Copy,
5496    /// Clipboard cut event.
5497    Cut,
5498    /// Clipboard paste event with the pasted text content.
5499    Paste(String),
5500    /// Input Method Editor event (e.g. CJK character composition)
5501    Ime(String),
5502    /// Touch began at the given position.
5503    TouchStart {
5504        x: f32,
5505        y: f32,
5506        touch_id: u64,
5507    },
5508    /// Touch moved to a new position.
5509    TouchMove {
5510        x: f32,
5511        y: f32,
5512        touch_id: u64,
5513    },
5514    /// Touch ended at the given position.
5515    TouchEnd {
5516        x: f32,
5517        y: f32,
5518        touch_id: u64,
5519    },
5520    /// Touch cancelled.
5521    TouchCancel {
5522        touch_id: u64,
5523    },
5524    /// Multi-touch pinch gesture.
5525    /// `center` is the gesture anchor point in device-independent pixels.
5526    /// `scale` is the relative pinch scale (>1 = expand, <1 = contract).
5527    /// `velocity` is the instantaneous velocity of the pinch.
5528    /// `phase` indicates the current phase of the gesture lifecycle.
5529    GesturePinch {
5530        center: [f32; 2],
5531        scale: f32,
5532        velocity: f32,
5533        phase: TouchPhase,
5534    },
5535    /// Multi-touch swipe/pan gesture.
5536    /// `direction` is the normalized direction vector [dx, dy].
5537    /// `velocity` is the instantaneous velocity of the swipe.
5538    /// `phase` indicates the current phase of the gesture lifecycle.
5539    GestureSwipe {
5540        direction: [f32; 2],
5541        velocity: f32,
5542        phase: TouchPhase,
5543    },
5544    /// Drag-and-drop: external file dropped onto window.
5545    FileDrop {
5546        x: f32,
5547        y: f32,
5548        path: String,
5549    },
5550}
5551
5552impl Event {
5553    /// Returns the input pointer precision value in physical pixels if applicable.
5554    ///
5555    /// WHY: Used to scale hit-testing bounding boxes for proximity matching.
5556    /// CONTRACT: Mouse pointer inputs return low precision values (close to 0.0px),
5557    /// whereas touch inputs return larger values (e.g., 150.0px) for finger emulation.
5558    pub fn pointer_precision(&self) -> f32 {
5559        match self {
5560            Self::PointerDown {
5561                pointer_precision, ..
5562            }
5563            | Self::PointerUp {
5564                pointer_precision, ..
5565            }
5566            | Self::PointerMove {
5567                pointer_precision, ..
5568            }
5569            | Self::PointerClick {
5570                pointer_precision, ..
5571            }
5572            | Self::PointerWheel {
5573                pointer_precision, ..
5574            }
5575            | Self::PointerDoubleClick {
5576                pointer_precision, ..
5577            }
5578            | Self::DragStart {
5579                pointer_precision, ..
5580            }
5581            | Self::DragMove {
5582                pointer_precision, ..
5583            }
5584            | Self::DragEnd {
5585                pointer_precision, ..
5586            } => *pointer_precision,
5587            _ => 0.0,
5588        }
5589    }
5590
5591    /// Returns the canonical string name of the event for lookup in handler maps.
5592    pub fn name(&self) -> &'static str {
5593        match self {
5594            Self::PointerDown { .. } => "pointerdown",
5595            Self::PointerUp { .. } => "pointerup",
5596            Self::PointerMove { .. } => "pointermove",
5597            Self::PointerClick { .. } => "pointerclick",
5598            Self::PointerEnter => "pointerenter",
5599            Self::PointerLeave => "pointerleave",
5600            Self::PointerWheel { .. } => "pointerwheel",
5601            Self::PointerDoubleClick { .. } => "pointerdoubleclick",
5602            Self::DragStart { .. } => "dragstart",
5603            Self::DragMove { .. } => "dragmove",
5604            Self::DragEnd { .. } => "dragend",
5605            Self::KeyDown { .. } => "keydown",
5606            Self::KeyUp { .. } => "keyup",
5607            Self::FocusIn => "focusin",
5608            Self::FocusOut => "focusout",
5609            Self::Copy => "copy",
5610            Self::Cut => "cut",
5611            Self::Paste(_) => "paste",
5612            Self::Ime(_) => "ime",
5613            Self::TouchStart { .. } => "touchstart",
5614            Self::TouchMove { .. } => "touchmove",
5615            Self::TouchEnd { .. } => "touchend",
5616            Self::TouchCancel { .. } => "touchcancel",
5617            Self::GesturePinch { .. } => "gesturepinch",
5618            Self::GestureSwipe { .. } => "gestureswipe",
5619            Self::FileDrop { .. } => "filedrop",
5620        }
5621    }
5622}
5623
5624
5625/// Response from an event handler
5626#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5627pub enum EventResponse {
5628    Handled,
5629    Ignored,
5630}
5631
5632// =========================================================================
5633// P1-40: EventPhase -- documents event propagation phases
5634// =========================================================================
5635//
5636// The CVKG event system follows the standard capture/target/bubble
5637// model used by the W3C DOM Event spec. When an event fires, it
5638// propagates through 3 phases:
5639//
5640// 1. Capture: the event travels from the root down to the
5641//    target's parent. Listeners registered for the capture
5642//    phase fire first.
5643// 2. Target: the event reaches the target node itself. Listeners
5644//    on the target fire (regardless of capture/bubble).
5645// 3. Bubble: the event travels back up from the target's
5646//    parent to the root. Listeners registered for the bubble
5647//    phase fire last.
5648//
5649// Cancellation: any handler can call Event::stop_propagation()
5650// to prevent the event from continuing to the next phase or
5651// the next node. This affects only the current event instance.
5652//
5653// Example: a click on a button inside a panel:
5654//  - panel's capture handler fires
5655//  - button's capture handler fires
5656//  - button's target handler fires
5657//  - button's bubble handler fires
5658//  - panel's bubble handler fires
5659//
5660// Use this enum when registering listeners to specify which
5661// phase to listen for.
5662#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5663pub enum EventPhase {
5664    /// Event is traveling from the root toward the target.
5665    Capture,
5666    /// Event has reached the target node.
5667    Target,
5668    /// Event is traveling from the target back toward the root.
5669    Bubble,
5670}
5671
5672impl EventPhase {
5673    /// All phases in propagation order.
5674    pub const ALL: [EventPhase; 3] = [
5675        EventPhase::Capture,
5676        EventPhase::Target,
5677        EventPhase::Bubble,
5678    ];
5679}
5680
5681/// A basic implementation of AssetManager that can be overridden by platform backends.
5682pub struct DefaultAssetManager {
5683    cache: AssetCache,
5684}
5685type AssetCache = Arc<arc_swap::ArcSwap<HashMap<String, AssetState<Arc<Vec<u8>>>>>>;
5686
5687impl Default for DefaultAssetManager {
5688    fn default() -> Self {
5689        Self::new()
5690    }
5691}
5692
5693impl DefaultAssetManager {
5694    pub fn new() -> Self {
5695        Self {
5696            cache: Arc::new(arc_swap::ArcSwap::from_pointee(HashMap::new())),
5697        }
5698    }
5699}
5700
5701impl AssetManager for DefaultAssetManager {
5702    fn load_image(&self, url: &str) -> AssetState<Arc<Vec<u8>>> {
5703        if let Some(state) = self.cache.load().get(url) {
5704            return state.clone();
5705        }
5706
5707        self.cache.rcu(|map| {
5708            let mut m = (**map).clone();
5709            m.entry(url.to_string()).or_insert(AssetState::Loading);
5710            m
5711        });
5712        AssetState::Loading
5713    }
5714
5715    fn preload_image(&self, _url: &str) {}
5716}
5717
5718use std::future::Future;
5719
5720/// Suspense wrapper for asynchronous state management.
5721/// Integrates with State<T> to provide loading/error/ready states for async operations.
5722pub struct Suspense<T: Clone + Send + Sync + 'static> {
5723    inner: State<AssetState<T>>,
5724}
5725
5726impl<T: Clone + Send + Sync + 'static> Default for Suspense<T> {
5727    fn default() -> Self {
5728        Self::new()
5729    }
5730}
5731
5732impl<T: Clone + Send + Sync + 'static> Suspense<T> {
5733    pub fn new() -> Self {
5734        Self {
5735            inner: State::new(AssetState::Loading),
5736        }
5737    }
5738
5739    pub fn new_async<F>(future: F) -> Self
5740    where
5741        F: Future<Output = Result<T, String>> + Send + 'static,
5742    {
5743        let suspense = Self::new();
5744        let suspense_clone = suspense.clone();
5745
5746        #[cfg(not(target_arch = "wasm32"))]
5747        {
5748            // P1-17 fix: use the shared fallback runtime instead of
5749            // spawning a new OS thread + runtime per call. If an
5750            // ambient tokio runtime exists, prefer it (preserves
5751            // caller intent). Otherwise use the shared fallback
5752            // runtime which is bounded to a small worker count.
5753            if let Ok(handle) = tokio::runtime::Handle::try_current() {
5754                handle.spawn(async move {
5755                    let result = future.await;
5756                    match result {
5757                        Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5758                        Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5759                    }
5760                });
5761            } else {
5762                fallback_runtime().spawn(async move {
5763                    let result = future.await;
5764                    match result {
5765                        Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5766                        Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5767                    }
5768                });
5769            }
5770        }
5771        #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
5772        {
5773            wasm_bindgen_futures::spawn_local(async move {
5774                let result = future.await;
5775                match result {
5776                    Ok(val) => suspense_clone.inner.set(AssetState::Ready(val)),
5777                    Err(err) => suspense_clone.inner.set(AssetState::Error(err)),
5778                }
5779            });
5780        }
5781
5782        suspense
5783    }
5784
5785    pub fn ready(value: T) -> Self {
5786        Self {
5787            inner: State::new(AssetState::Ready(value)),
5788        }
5789    }
5790
5791    pub fn error(message: impl Into<String>) -> Self {
5792        Self {
5793            inner: State::new(AssetState::Error(message.into())),
5794        }
5795    }
5796
5797    pub fn get(&self) -> AssetState<T> {
5798        self.inner.get()
5799    }
5800
5801    pub fn get_ref(&self) -> AssetState<T> {
5802        self.inner.get()
5803    }
5804
5805    pub fn is_loading(&self) -> bool {
5806        matches!(self.get(), AssetState::Loading)
5807    }
5808
5809    pub fn is_ready(&self) -> bool {
5810        matches!(self.get(), AssetState::Ready(_))
5811    }
5812
5813    pub fn is_error(&self) -> bool {
5814        matches!(self.get(), AssetState::Error(_))
5815    }
5816
5817    pub fn ready_value(&self) -> Option<T> {
5818        match self.get() {
5819            AssetState::Ready(value) => Some(value),
5820            _ => None,
5821        }
5822    }
5823
5824    pub fn error_message(&self) -> Option<String> {
5825        match self.get() {
5826            AssetState::Error(message) => Some(message),
5827            _ => None,
5828        }
5829    }
5830
5831    pub fn subscribe<F: Fn(&AssetState<T>) + Send + Sync + 'static>(&self, callback: F) {
5832        self.inner.subscribe(callback)
5833    }
5834
5835    pub fn inner_state(&self) -> &State<AssetState<T>> {
5836        &self.inner
5837    }
5838}
5839
5840impl<T: Clone + Send + Sync + 'static> Clone for Suspense<T> {
5841    fn clone(&self) -> Self {
5842        Self {
5843            inner: self.inner.clone(),
5844        }
5845    }
5846}
5847
5848impl<T: Clone + Send + Sync + 'static> From<T> for Suspense<T> {
5849    fn from(value: T) -> Self {
5850        Self::ready(value)
5851    }
5852}
5853
5854impl<T: Clone + Send + Sync + 'static> From<Result<T, String>> for Suspense<T> {
5855    fn from(result: Result<T, String>) -> Self {
5856        match result {
5857            Ok(value) => Self::ready(value),
5858            Err(error) => Self::error(error),
5859        }
5860    }
5861}
5862
5863#[cfg(test)]
5864mod phase1_test;
5865
5866/// Berserker mode states for the rendering pipeline.
5867#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5868pub enum RenderIntensityMode {
5869    Normal,
5870    Rage,    // Red tint, slight shake
5871    Frenzy,  // Heavy red tint, motion blur, aggressive shake
5872    GodMode, // Golden aura, lightning arcs
5873}
5874
5875/// Seer trait for AI-assisted UI components.
5876/// Allows components to receive "prophecies" (predictions) from an AI backend.
5877pub trait Seer: Send + Sync {
5878    /// Provide a prediction for the next user action or content.
5879    fn predict(&self, context: &str) -> String;
5880    /// Stream real-time "whispers" (transcriptions/intent).
5881    fn whispers(&self) -> Vec<String>;
5882}
5883
5884#[cfg(test)]
5885mod vili_tests {
5886    use super::*;
5887
5888    struct DummyRenderer;
5889    impl ElapsedTime for DummyRenderer {
5890        fn elapsed_time(&self) -> f32 {
5891            0.0
5892        }
5893        fn delta_time(&self) -> f32 {
5894            0.0
5895        }
5896    }
5897    impl Renderer for DummyRenderer {
5898        fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
5899        fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
5900        fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
5901        fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5902        fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
5903        fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
5904        fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
5905
5906        fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
5907        fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {
5908        }
5909        fn set_camera_3d(&mut self, _camera: &Camera3D) {}
5910        fn push_transform_3d(&mut self, _transform: &Transform3D) {}
5911        fn pop_transform_3d(&mut self) {}
5912    }
5913
5914    #[test]
5915    fn test_magnetic_warp() {
5916        let renderer = DummyRenderer;
5917        let anchor = Rect {
5918            x: 100.0,
5919            y: 100.0,
5920            width: 50.0,
5921            height: 50.0,
5922        };
5923        // Pointer is near the anchor (distance < 120)
5924        let pointer = [125.0, 50.0];
5925        // distance from center (125, 125) is 75.
5926        // force = (1.0 - 75/120) * strength
5927        let warp = renderer.magnetic_warp(pointer, anchor, 1.0);
5928        // It should pull closer to (125, 125), so Y should be > 50
5929        assert!(warp[1] > 50.0);
5930
5931        // Out of range pointer should remain unchanged
5932        let far_pointer = [500.0, 500.0];
5933        let far_warp = renderer.magnetic_warp(far_pointer, anchor, 1.0);
5934        assert_eq!(far_pointer, far_warp);
5935    }
5936
5937    #[test]
5938    fn test_mani_glow() {
5939        let renderer = DummyRenderer;
5940        let bounds = Rect {
5941            x: 0.0,
5942            y: 0.0,
5943            width: 100.0,
5944            height: 100.0,
5945        };
5946        let pointer_inside = [50.0, 50.0];
5947        let glow_max = renderer.mani_glow_intensity(pointer_inside, bounds, 120.0);
5948        assert_eq!(glow_max, 1.0);
5949
5950        let pointer_edge = [50.0, -10.0];
5951        let glow_partial = renderer.mani_glow_intensity(pointer_edge, bounds, 120.0);
5952        assert!(glow_partial > 0.0 && glow_partial < 1.0);
5953    }
5954
5955    #[test]
5956    fn test_fafnir_evolve() {
5957        let renderer = DummyRenderer;
5958        let bounds = Rect {
5959            x: 0.0,
5960            y: 0.0,
5961            width: 100.0,
5962            height: 100.0,
5963        };
5964        let pointer_inside = [50.0, 50.0];
5965        let scale = renderer.fafnir_evolve(pointer_inside, bounds, 1.2);
5966        assert_eq!(scale, 1.2); // Full scale when hovering center
5967    }
5968
5969    #[test]
5970    fn test_undo_manager_basic() {
5971        let mut manager = UndoManager::new(3, 0.5);
5972        let val = std::sync::Arc::new(std::sync::Mutex::new(0));
5973
5974        let v1 = val.clone();
5975        let v2 = val.clone();
5976        manager.push(
5977            "Add",
5978            move || *v1.lock().unwrap() -= 1,
5979            move || *v2.lock().unwrap() += 1,
5980        );
5981
5982        assert!(manager.can_undo());
5983        assert!(!manager.can_redo());
5984
5985        let undo = manager.undo().unwrap();
5986        undo();
5987        assert_eq!(*val.lock().unwrap(), -1);
5988        assert!(!manager.can_undo());
5989        assert!(manager.can_redo());
5990
5991        let redo = manager.redo().unwrap();
5992        redo();
5993        assert_eq!(*val.lock().unwrap(), 0);
5994    }
5995
5996    #[test]
5997    fn test_undo_manager_depth_limit() {
5998        let mut manager = UndoManager::new(2, 0.5);
5999        manager.push("1", || {}, || {});
6000        manager.push("2", || {}, || {});
6001        manager.push("3", || {}, || {});
6002
6003        assert_eq!(manager.stack.len(), 2);
6004        assert_eq!(manager.position, 2);
6005    }
6006
6007    #[test]
6008    fn test_undo_manager_coalescing() {
6009        let mut manager = UndoManager::new(10, 1.0);
6010        let count = std::sync::Arc::new(std::sync::Mutex::new(0));
6011
6012        let c = count.clone();
6013        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
6014
6015        let c = count.clone();
6016        manager.push_coalesceable("Type", move || *c.lock().unwrap() -= 1, || {});
6017
6018        assert_eq!(manager.stack.len(), 1);
6019
6020        let undo = manager.undo().unwrap();
6021        undo();
6022        assert_eq!(*count.lock().unwrap(), -2);
6023    }
6024}
6025
6026#[cfg(test)]
6027mod error_boundary_tests {
6028    use super::*;
6029
6030    /// A trivial view that renders successfully.
6031    struct SuccessView;
6032
6033    impl View for SuccessView {
6034        type Body = Never;
6035        fn body(self) -> Never {
6036            // SAFETY: `Never` is uninhabitable -- test helper with no composable body.
6037            unreachable!()
6038        }
6039        fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {
6040            // No-op -- renders successfully.
6041        }
6042    }
6043
6044    /// A view that panics during render.
6045    struct PanicOnRender;
6046
6047    impl View for PanicOnRender {
6048        type Body = Never;
6049        fn body(self) -> Never {
6050            // SAFETY: `Never` is uninhabitable -- test helper that only panics in render().
6051            unreachable!()
6052        }
6053        fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {
6054            panic!("intentional render panic");
6055        }
6056    }
6057
6058    /// A view that panics during intrinsic_size.
6059    struct PanicOnSize;
6060
6061    impl View for PanicOnSize {
6062        type Body = Never;
6063        fn body(self) -> Never {
6064            // SAFETY: `Never` is uninhabitable -- test helper that only panics in intrinsic_size().
6065            unreachable!()
6066        }
6067        fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {
6068            // Render succeeds, but size panics.
6069        }
6070        fn intrinsic_size(&self, _renderer: &mut dyn Renderer, _proposal: SizeProposal) -> Size {
6071            panic!("intentional size panic");
6072        }
6073    }
6074
6075    /// A view that panics with a String payload.
6076    struct PanicWithString;
6077
6078    impl View for PanicWithString {
6079        type Body = Never;
6080        fn body(self) -> Never {
6081            // SAFETY: `Never` is uninhabitable -- test helper that panics with a String payload.
6082            unreachable!()
6083        }
6084        fn render(&self, _renderer: &mut dyn Renderer, _rect: Rect) {
6085            panic!("{}", "custom error message".to_string());
6086        }
6087    }
6088
6089    struct DummyRenderer;
6090    impl ElapsedTime for DummyRenderer {
6091        fn elapsed_time(&self) -> f32 {
6092            0.0
6093        }
6094        fn delta_time(&self) -> f32 {
6095            0.0
6096        }
6097    }
6098    impl Renderer for DummyRenderer {
6099        fn fill_rect(&mut self, _r: Rect, _c: [f32; 4]) {}
6100        fn fill_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4]) {}
6101        fn fill_ellipse(&mut self, _r: Rect, _c: [f32; 4]) {}
6102        fn stroke_rect(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
6103        fn stroke_rounded_rect(&mut self, _r: Rect, _rad: f32, _c: [f32; 4], _w: f32) {}
6104        fn stroke_ellipse(&mut self, _r: Rect, _c: [f32; 4], _w: f32) {}
6105        fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
6106
6107        fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
6108        fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {
6109        }
6110        fn set_camera_3d(&mut self, _camera: &Camera3D) {}
6111        fn push_transform_3d(&mut self, _transform: &Transform3D) {}
6112        fn pop_transform_3d(&mut self) {}
6113    }
6114
6115    const TEST_RECT: Rect = Rect {
6116        x: 0.0,
6117        y: 0.0,
6118        width: 200.0,
6119        height: 100.0,
6120    };
6121
6122    #[test]
6123    fn error_boundary_renders_child_on_success() {
6124        let boundary = ErrorBoundary::new(SuccessView);
6125        let mut renderer = DummyRenderer;
6126
6127        boundary.render(&mut renderer, TEST_RECT);
6128
6129        assert!(
6130            !boundary.has_error(),
6131            "should not have error after successful render"
6132        );
6133        assert!(
6134            boundary.last_error().is_none(),
6135            "should have no error message"
6136        );
6137    }
6138
6139    #[test]
6140    fn error_boundary_catches_render_panic() {
6141        let boundary = ErrorBoundary::new(PanicOnRender);
6142        let mut renderer = DummyRenderer;
6143
6144        // This must NOT panic -- the boundary catches it.
6145        boundary.render(&mut renderer, TEST_RECT);
6146
6147        assert!(
6148            boundary.has_error(),
6149            "should have error after catching panic"
6150        );
6151        let err = boundary.last_error().expect("should have error message");
6152        assert!(
6153            err.contains("intentional render panic"),
6154            "error message should contain panic message, got: {err}"
6155        );
6156    }
6157
6158    #[test]
6159    fn error_boundary_catches_size_panic() {
6160        let boundary = ErrorBoundary::new(PanicOnSize);
6161        let mut renderer = DummyRenderer;
6162        let proposal = layout::SizeProposal {
6163            width: Some(100.0),
6164            height: Some(50.0),
6165        };
6166
6167        let size = boundary.intrinsic_size(&mut renderer, proposal);
6168
6169        assert!(
6170            boundary.has_error(),
6171            "should have error after catching size panic"
6172        );
6173        assert_eq!(size, Size::ZERO, "fallback size should be zero");
6174    }
6175
6176    #[test]
6177    fn error_boundary_catches_string_panic() {
6178        let boundary = ErrorBoundary::new(PanicWithString);
6179        let mut renderer = DummyRenderer;
6180
6181        boundary.render(&mut renderer, TEST_RECT);
6182
6183        assert!(boundary.has_error());
6184        let err = boundary.last_error().expect("should have error message");
6185        assert!(
6186            err.contains("custom error message"),
6187            "should capture String panic payload, got: {err}"
6188        );
6189    }
6190
6191    #[test]
6192    fn error_boundary_clear_error_resets_state() {
6193        let boundary = ErrorBoundary::new(PanicOnRender);
6194        let mut renderer = DummyRenderer;
6195
6196        boundary.render(&mut renderer, TEST_RECT);
6197        assert!(boundary.has_error());
6198
6199        boundary.clear_error();
6200        assert!(
6201            !boundary.has_error(),
6202            "should be clear after clear_error()"
6203        );
6204        assert!(
6205            boundary.last_error().is_none(),
6206            "error message should be cleared"
6207        );
6208    }
6209
6210    #[test]
6211    fn error_boundary_fallback_color_is_configurable() {
6212        let boundary = ErrorBoundary::new(SuccessView)
6213            .fallback_color([0.0, 0.0, 1.0, 1.0])
6214            .fallback_label("custom label");
6215
6216        assert_eq!(boundary.fallback_color, [0.0, 0.0, 1.0, 1.0]);
6217        assert_eq!(
6218            boundary.fallback_label.as_deref(),
6219            Some("custom label")
6220        );
6221    }
6222
6223    #[test]
6224    fn error_boundary_flex_weight_delegates_to_child() {
6225        let boundary = ErrorBoundary::new(SuccessView);
6226        assert_eq!(boundary.flex_weight(), 0.0, "should delegate to child (default 0.0)");
6227    }
6228
6229    #[test]
6230    fn error_boundary_body_delegates_to_child() {
6231        // body() must be pure and delegate directly.
6232        let _boundary = ErrorBoundary::new(SuccessView);
6233        // Calling body() should not panic and should return Never (unreachable).
6234        // We test this indirectly -- if it compiles and the Never type is correct,
6235        // the body() call would diverge. We just verify the type compiles.
6236        let _boundary_type = std::any::type_name::<ErrorBoundary<SuccessView>>();
6237    }
6238
6239    /// Renderer that tracks stack-pushing operations so tests can verify
6240    /// ErrorBoundary restores renderer state on panic.
6241    struct TrackingRenderer {
6242        clip_depth: u32,
6243        opacity_depth: u32,
6244        shadow_depth: u32,
6245    }
6246
6247    impl TrackingRenderer {
6248        fn new() -> Self {
6249            Self {
6250                clip_depth: 0,
6251                opacity_depth: 0,
6252                shadow_depth: 0,
6253            }
6254        }
6255    }
6256
6257    impl Renderer for TrackingRenderer {
6258        fn fill_rect(&mut self, _rect: Rect, _color: [f32; 4]) {}
6259        fn fill_rounded_rect(&mut self, _rect: Rect, _radius: f32, _color: [f32; 4]) {}
6260        fn fill_ellipse(&mut self, _rect: Rect, _color: [f32; 4]) {}
6261        fn stroke_rect(&mut self, _rect: Rect, _color: [f32; 4], _w: f32) {}
6262        fn stroke_rounded_rect(
6263            &mut self,
6264            _rect: Rect,
6265            _radius: f32,
6266            _color: [f32; 4],
6267            _stroke_width: f32,
6268        ) {
6269        }
6270        fn stroke_ellipse(&mut self, _rect: Rect, _color: [f32; 4], _stroke_width: f32) {}
6271        fn draw_line(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, _c: [f32; 4], _w: f32) {}
6272
6273        fn push_clip_rect(&mut self, _rect: Rect) {
6274            self.clip_depth += 1;
6275        }
6276        fn pop_clip_rect(&mut self) {
6277            self.clip_depth = self.clip_depth.saturating_sub(1);
6278        }
6279        fn push_opacity(&mut self, _opacity: f32) {
6280            self.opacity_depth += 1;
6281        }
6282        fn pop_opacity(&mut self) {
6283            self.opacity_depth = self.opacity_depth.saturating_sub(1);
6284        }
6285        fn push_shadow(&mut self, _r: f32, _c: [f32; 4], _o: [f32; 2]) {
6286            self.shadow_depth += 1;
6287        }
6288        fn pop_shadow(&mut self) {
6289            self.shadow_depth = self.shadow_depth.saturating_sub(1);
6290        }
6291        fn memoize(&mut self, _id: u64, _hash: u64, _r: &dyn Fn(&mut dyn Renderer)) {}
6292        fn snapshot_render_state(&self) -> RenderStateSnapshot {
6293            // Note: cannot mutate self in &self method; we record that it was
6294            // called via a different channel (the test counts calls on a Cell).
6295            RenderStateSnapshot {
6296                clip_depth: self.clip_depth,
6297                opacity_depth: self.opacity_depth,
6298                slice_depth: 0,
6299                shadow_depth: self.shadow_depth,
6300                transform_depth: 0,
6301                vnode_depth: 0,
6302            }
6303        }
6304        fn restore_render_state(&mut self, snap: RenderStateSnapshot) {
6305            self.clip_depth = snap.clip_depth;
6306            self.opacity_depth = snap.opacity_depth;
6307            self.shadow_depth = snap.shadow_depth;
6308        }
6309        fn draw_mesh_3d(&mut self, _mesh: &Mesh, _material: &Material3D, _transform: &Transform3D) {}
6310        fn set_camera_3d(&mut self, _camera: &Camera3D) {}
6311        fn push_transform_3d(&mut self, _transform: &Transform3D) {}
6312        fn pop_transform_3d(&mut self) {}
6313    }
6314
6315    impl ElapsedTime for TrackingRenderer {
6316        fn elapsed_time(&self) -> f32 {
6317            0.0
6318        }
6319        fn delta_time(&self) -> f32 {
6320            0.0
6321        }
6322    }
6323
6324    /// View that pushes clip/opacity/shadow stacks and then panics.
6325    /// After ErrorBoundary restores state, the renderer should have no leftover
6326    /// pushed items.
6327    struct StackPushingPanicView;
6328
6329    impl View for StackPushingPanicView {
6330        type Body = Never;
6331        fn body(self) -> Never {
6332            unreachable!()
6333        }
6334        fn render(&self, renderer: &mut dyn Renderer, _rect: Rect) {
6335            renderer.push_clip_rect(Rect::new(0.0, 0.0, 50.0, 50.0));
6336            renderer.push_opacity(0.5);
6337            renderer.push_shadow(2.0, [0.0, 0.0, 0.0, 0.5], [0.0, 0.0]);
6338            panic!("intentional stack-pushing panic");
6339        }
6340    }
6341
6342    #[test]
6343    fn error_boundary_restores_renderer_state_on_panic() {
6344        // Regression test for P0-5: ErrorBoundary must restore renderer
6345        // stack state after a mid-render panic so siblings drawn afterward
6346        // don't inherit leaked clip/opacity/transform/etc. state.
6347        let boundary = ErrorBoundary::new(StackPushingPanicView);
6348        let mut renderer = TrackingRenderer::new();
6349
6350        // Pre-snapshot: empty stacks.
6351        let snap_before = renderer.snapshot_render_state();
6352        assert_eq!(snap_before.clip_depth, 0);
6353        assert_eq!(snap_before.opacity_depth, 0);
6354        assert_eq!(snap_before.shadow_depth, 0);
6355
6356        // Render -- child panics, boundary must catch and restore.
6357        boundary.render(&mut renderer, TEST_RECT);
6358
6359        // Verify the panic was caught and state was restored.
6360        assert!(boundary.has_error(), "should have caught the panic");
6361        let snap_after = renderer.snapshot_render_state();
6362        assert_eq!(
6363            snap_after.clip_depth, 0,
6364            "clip stack should be restored to empty after panic"
6365        );
6366        assert_eq!(
6367            snap_after.opacity_depth, 0,
6368            "opacity stack should be restored to empty after panic"
6369        );
6370        assert_eq!(
6371            snap_after.shadow_depth, 0,
6372            "shadow stack should be restored to empty after panic"
6373        );
6374    }
6375
6376    #[test]
6377    fn render_state_snapshot_default_is_zeroed() {
6378        // The default snapshot must be all-zero so backends without
6379        // stack state can use it as a sentinel.
6380        let snap = RenderStateSnapshot::default();
6381        assert_eq!(snap.clip_depth, 0);
6382        assert_eq!(snap.opacity_depth, 0);
6383        assert_eq!(snap.slice_depth, 0);
6384        assert_eq!(snap.shadow_depth, 0);
6385        assert_eq!(snap.transform_depth, 0);
6386        assert_eq!(snap.vnode_depth, 0);
6387    }
6388
6389    #[test]
6390    fn render_state_snapshot_round_trip() {
6391        let snap = RenderStateSnapshot {
6392            clip_depth: 3,
6393            opacity_depth: 2,
6394            slice_depth: 1,
6395            shadow_depth: 0,
6396            transform_depth: 4,
6397            vnode_depth: 5,
6398        };
6399        let copied = snap;
6400        assert_eq!(copied, snap);
6401    }
6402}
6403
6404// =============================================================================
6405// THEME CONTEXT -- Thread-local theme access for components
6406// =============================================================================
6407//
6408// Components call `use_theme()` to get the current SemanticColors.
6409// The native renderer sets this via `set_current_theme()` before each frame.
6410// Falls back to dark theme defaults if no theme has been set.
6411//
6412// We store SemanticColors directly (not the full Theme) to avoid depending
6413// on cvkg-themes from cvkg-core. The colors are cloned into thread-local storage.
6414
6415use std::cell::RefCell;
6416
6417thread_local! {
6418    /// Thread-local theme context for the current frame.
6419    static THEME_CONTEXT: RefCell<Option<ThemeContext>> = const { RefCell::new(None) };
6420}
6421
6422/// Theme context available to components during render.
6423/// Includes both semantic colors and visual effect flags.
6424#[derive(Debug, Clone)]
6425pub struct ThemeContext {
6426    /// Semantic colors for the current theme.
6427    pub colors: color::SemanticColors,
6428    /// If true, components may use glassmorphic effects (frosted glass, blur).
6429    /// If false, components should render with solid backgrounds.
6430    pub glassmorphism_enabled: bool,
6431}
6432
6433impl ThemeContext {
6434    /// Create a dark theme context with glassmorphism enabled.
6435    pub fn dark() -> Self {
6436        Self {
6437            colors: color::SemanticColors::dark(),
6438            glassmorphism_enabled: true,
6439        }
6440    }
6441
6442    /// Create a light theme context with glassmorphism disabled.
6443    pub fn light() -> Self {
6444        Self {
6445            colors: color::SemanticColors::light(),
6446            glassmorphism_enabled: false,
6447        }
6448    }
6449}
6450
6451/// Set the current theme context for this thread.
6452/// Called by the native renderer before each frame.
6453pub fn set_current_theme(colors: color::SemanticColors) {
6454    THEME_CONTEXT.with(|cell| {
6455        let is_light = (colors.background.r + colors.background.g + colors.background.b) / 3.0 > 0.5;
6456        let glassmorphism = !is_light; // light themes default to no glassmorphism
6457        *cell.borrow_mut() = Some(ThemeContext { colors, glassmorphism_enabled: glassmorphism });
6458    });
6459}
6460
6461/// Set the full theme context (including glassmorphism flag).
6462pub fn set_theme_context(ctx: ThemeContext) {
6463    THEME_CONTEXT.with(|cell| {
6464        *cell.borrow_mut() = Some(ctx);
6465    });
6466}
6467
6468/// Clear the current theme. Called after each frame.
6469pub fn clear_current_theme() {
6470    THEME_CONTEXT.with(|cell| {
6471        *cell.borrow_mut() = None;
6472    });
6473}
6474
6475/// Access the current semantic colors from within a component's `render()` method.
6476///
6477/// Returns the colors set by the most recent `set_current_theme()` call.
6478/// Falls back to dark theme defaults if no theme has been set.
6479///
6480/// # Example
6481/// ```no_run
6482/// use cvkg_core::{use_theme, Renderer, Rect};
6483///
6484/// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
6485///     let colors = use_theme();
6486///     renderer.fill_rounded_rect(rect, 8.0, [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
6487/// }
6488/// ```
6489pub fn use_theme() -> color::SemanticColors {
6490    THEME_CONTEXT.with(|cell| {
6491        cell.borrow()
6492            .clone()
6493            .map(|ctx| ctx.colors)
6494            .unwrap_or_else(color::SemanticColors::dark)
6495    })
6496}
6497
6498/// Access the full theme context from within a component's `render()` method.
6499///
6500/// Returns the current `ThemeContext` including both colors and effect flags.
6501/// Falls back to dark theme defaults if no theme has been set.
6502pub fn use_theme_context() -> ThemeContext {
6503    THEME_CONTEXT.with(|cell| {
6504        cell.borrow()
6505            .clone()
6506            .unwrap_or_else(ThemeContext::dark)
6507    })
6508}
6509
6510/// Returns true if glassmorphic effects are enabled in the current theme.
6511/// Components should check this before calling `renderer.bifrost()`.
6512pub fn glassmorphism_enabled() -> bool {
6513    THEME_CONTEXT.with(|cell| {
6514        cell.borrow()
6515            .as_ref()
6516            .map(|ctx| ctx.glassmorphism_enabled)
6517            .unwrap_or(true) // default: glassmorphism on (dark theme)
6518    })
6519}
6520
6521// =============================================================================
6522// COLOR MODULE -- Standalone semantic colors type
6523// =============================================================================
6524//
6525// This module provides `SemanticColors`, a self-contained color palette that
6526// components can use without depending on `cvkg-themes`. The `use_theme()`
6527// function returns the current `SemanticColors` from thread-local storage.
6528
6529pub mod color {
6530    use super::Color;
6531
6532    /// A complete set of semantic colors for UI components.
6533    ///
6534    /// Each color serves a specific role in the UI. Components should reference
6535    /// these semantic roles rather than hardcoding RGBA values.
6536    ///
6537    /// # Example
6538    /// ```no_run
6539    /// use cvkg_core::{use_theme, Renderer, Rect};
6540    ///
6541    /// fn render_button(renderer: &mut dyn Renderer, rect: Rect) {
6542    ///     let colors = use_theme();
6543    ///     // Use accent color for the button background
6544    ///     renderer.fill_rounded_rect(rect, 8.0,
6545    ///         [colors.accent.r, colors.accent.g, colors.accent.b, colors.accent.a]);
6546    /// }
6547    /// ```
6548    #[derive(Debug, Clone)]
6549    pub struct SemanticColors {
6550        /// Primary brand color -- used for key interactive elements.
6551        pub primary: Color,
6552        /// Secondary color -- used for less prominent interactive elements.
6553        pub secondary: Color,
6554        /// Accent color -- used for highlights, focus rings, CTAs.
6555        pub accent: Color,
6556        /// Page/window background color.
6557        pub background: Color,
6558        /// Surface color -- used for cards, panels, sheets.
6559        pub surface: Color,
6560        /// Error color -- used for destructive actions, error messages.
6561        pub error: Color,
6562        /// Warning color -- used for caution indicators.
6563        pub warning: Color,
6564        /// Success color -- used for positive feedback.
6565        pub success: Color,
6566        /// Primary text color.
6567        pub text: Color,
6568        /// Dimmed/disabled text color.
6569        pub text_dim: Color,
6570    }
6571
6572    impl SemanticColors {
6573        /// Dark theme semantic colors (default fallback).
6574        pub fn dark() -> Self {
6575            Self {
6576                primary: Color::new(1.0, 0.84, 0.0, 1.0),      // Viking Gold
6577                secondary: Color::new(1.0, 0.0, 1.0, 1.0),     // Magenta Liquid
6578                accent: Color::new(1.0, 0.0, 0.4, 1.0),        // Crimson Flash
6579                background: Color::new(0.02, 0.02, 0.05, 1.0), // Deep Void
6580                surface: Color::new(0.05, 0.05, 0.07, 1.0),    // Tactical Obsidian
6581                error: Color::new(1.0, 0.2, 0.2, 1.0),         // Red
6582                warning: Color::new(1.0, 0.8, 0.0, 1.0),       // Yellow
6583                success: Color::new(0.0, 1.0, 0.5, 1.0),       // Green
6584                text: Color::new(0.95, 0.95, 1.0, 1.0),        // Near-white
6585                text_dim: Color::new(0.6, 0.6, 0.7, 1.0),      // Gray
6586            }
6587        }
6588
6589        /// Light theme semantic colors.
6590        pub fn light() -> Self {
6591            Self {
6592                primary: Color::new(0.35, 0.30, 0.70, 1.0),
6593                secondary: Color::new(0.30, 0.50, 0.30, 1.0),
6594                accent: Color::new(0.30, 0.35, 0.75, 1.0),
6595                background: Color::new(0.97, 0.97, 0.98, 1.0),
6596                surface: Color::new(0.93, 0.93, 0.95, 1.0),
6597                error: Color::new(0.75, 0.15, 0.15, 1.0),
6598                warning: Color::new(0.80, 0.60, 0.0, 1.0),
6599                success: Color::new(0.15, 0.65, 0.30, 1.0),
6600                text: Color::new(0.08, 0.08, 0.10, 1.0),
6601                text_dim: Color::new(0.40, 0.40, 0.45, 1.0),
6602            }
6603        }
6604
6605        /// Convert the accent color semantic color into interactive state colors.
6606        ///
6607        /// This provides hover/active/focus/disabled variants derived from the
6608        /// accent color, matching the pattern that `cvkg-themes::StateColors` uses.
6609        pub fn accent_states(&self) -> InteractiveColorStates {
6610            InteractiveColorStates::from_color(self.accent)
6611        }
6612
6613        /// Convert the primary color into interactive state colors.
6614        pub fn primary_states(&self) -> InteractiveColorStates {
6615            InteractiveColorStates::from_color(self.primary)
6616        }
6617
6618        /// Convert the error color into interactive state colors.
6619        pub fn error_states(&self) -> InteractiveColorStates {
6620            InteractiveColorStates::from_color(self.error)
6621        }
6622
6623        /// Convert the success color into interactive state colors.
6624        pub fn success_states(&self) -> InteractiveColorStates {
6625            InteractiveColorStates::from_color(self.success)
6626        }
6627    }
6628
6629    /// Interactive state colors derived from a single base color.
6630    ///
6631    /// Provides hover/active/focus/disabled variants for any color,
6632    /// derived via simple lightness adjustments in sRGB space.
6633    #[derive(Debug, Clone)]
6634    pub struct InteractiveColorStates {
6635        pub default: Color,
6636        pub hover: Color,
6637        pub active: Color,
6638        pub focus: Color,
6639        pub disabled: Color,
6640        pub focus_ring: Color,
6641    }
6642
6643    impl InteractiveColorStates {
6644        /// Derive interactive state colors from a base sRGB color.
6645        ///
6646        /// Uses simple lightness adjustments:
6647        /// - Hover: +15% lightness
6648        /// - Active: -15% lightness
6649        /// - Focus: same as default
6650        /// - Disabled: 40% opacity
6651        /// - Focus ring: base color at 70% opacity
6652        pub fn from_color(base: Color) -> Self {
6653            Self {
6654                default: base,
6655                hover: base.lighten(0.15),
6656                active: base.darken(0.15),
6657                focus: base,
6658                disabled: Color::new(base.r, base.g, base.b, base.a * 0.4),
6659                focus_ring: Color::new(base.r, base.g, base.b, base.a * 0.7),
6660            }
6661        }
6662
6663        /// Get the color for a specific interactive state.
6664        pub fn color_for(&self, state: InteractiveState) -> Color {
6665            match state {
6666                InteractiveState::Default => self.default,
6667                InteractiveState::Hover => self.hover,
6668                InteractiveState::Active => self.active,
6669                InteractiveState::Focus => self.focus,
6670                InteractiveState::Disabled => self.disabled,
6671            }
6672        }
6673    }
6674
6675    /// Interactive state for a component.
6676    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6677    pub enum InteractiveState {
6678        Default,
6679        Hover,
6680        Active,
6681        Focus,
6682        Disabled,
6683    }
6684}
6685
6686// =============================================================================
6687// USE_STATE HOOK -- Local component state with automatic re-render
6688// =============================================================================
6689//
6690// Components call `use_state(id, initial)` to get a `(getter, setter)` pair.
6691// The setter updates the global system state and triggers a re-render.
6692//
6693// This is the minimal state primitive needed for interactive components.
6694// For complex state, use the global `AppState` directly.
6695
6696/// Local state hook for components.
6697///
6698/// Returns a `(getter, setter)` pair:
6699/// - `getter()` returns the current value of type `T`
6700/// - `setter(value)` updates the value and triggers a re-render
6701///
6702/// The `id` must be unique per component instance (use a hash of the
6703/// component's label or a generated UUID).
6704pub fn use_state<T: Clone + Send + Sync + 'static>(
6705    id: u64,
6706    initial: T,
6707) -> (impl Fn() -> T, impl Fn(T)) {
6708    // Initialize the state if not already present
6709    let already_exists = load_system_state().get_component_state::<T>(id).is_some();
6710    if !already_exists {
6711        update_system_state(|s| {
6712            let mut ns = s.clone();
6713            ns.set_component_state(id, initial.clone());
6714            ns
6715        });
6716    }
6717
6718    let getter = move || -> T {
6719        load_system_state()
6720            .get_component_state::<T>(id)
6721            .map(|arc_lock| {
6722                arc_lock
6723                    .read()
6724                    .ok()
6725                    .map(|guard| (*guard).clone())
6726                    .unwrap_or_else(|| initial.clone())
6727            })
6728            .unwrap_or_else(|| initial.clone())
6729    };
6730
6731    let setter = {
6732        move |value| {
6733            update_system_state(|s| {
6734                let mut ns = s.clone();
6735                ns.set_component_state(id, value);
6736                ns
6737            });
6738        }
6739    };
6740
6741    (getter, setter)
6742}
6743
6744/// Generate a stable hash ID from a string key.
6745///
6746/// Use this to create unique IDs for `use_state` based on component labels
6747/// or other stable identifiers.
6748///
6749/// # Example
6750/// ```no_run
6751/// use cvkg_core::{use_state, use_state_hash};
6752/// let id = use_state_hash("my-checkbox");
6753/// let (value, set_value) = use_state(id, false);
6754/// ```
6755pub fn use_state_hash(key: &str) -> u64 {
6756    use std::hash::{Hash, Hasher};
6757    let mut s = std::collections::hash_map::DefaultHasher::new();
6758    key.hash(&mut s);
6759    s.finish()
6760}
6761
6762// =============================================================================
6763// ACCESSIBILITY PREFERENCES -- System accessibility settings
6764// =============================================================================
6765//
6766// Components and the renderer query these to adapt behavior:
6767// - Reduce Motion: disable non-essential animations
6768// - Reduce Transparency: replace glass materials with opaque surfaces
6769// - Increase Contrast: make borders visible, minimum alpha 0.5
6770
6771thread_local! {
6772    /// Thread-local accessibility preferences.
6773    /// Defaults to no restrictions (all false).
6774    static ACCESSIBILITY_PREFS: std::cell::RefCell<AccessibilityPreferences> =
6775        std::cell::RefCell::new(AccessibilityPreferences::default());
6776}
6777
6778/// System accessibility preferences that components and the renderer must honor.
6779///
6780/// These map to macOS System Settings > Accessibility:
6781/// - `reduce_motion`: Disables non-essential animations (spring, bounce, etc.)
6782/// - `reduce_transparency`: Replaces glass/transparent materials with opaque surfaces
6783/// - `increase_contrast`: Makes all borders visible, minimum alpha 0.5 for all elements
6784#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6785pub struct AccessibilityPreferences {
6786    /// User prefers reduced motion. Animations should be instant or very short.
6787    pub reduce_motion: bool,
6788    /// User prefers reduced transparency. Glass materials should be opaque.
6789    pub reduce_transparency: bool,
6790    /// User prefers increased contrast. Borders must be visible, min alpha 0.5.
6791    pub increase_contrast: bool,
6792}
6793
6794impl AccessibilityPreferences {
6795    /// Detect system accessibility preferences (macOS).
6796    ///
6797    /// On non-macOS platforms, returns defaults (all false).
6798    /// In a production implementation, this would query the OS APIs.
6799    pub fn detect_from_system() -> Self {
6800        #[cfg(target_os = "macos")]
6801        {
6802            // Try to read macOS accessibility preferences via defaults command
6803            let reduce_motion = std::process::Command::new("defaults")
6804                .args(["read", "-g", "com.apple.universalaccess", "reduceMotion"])
6805                .output()
6806                .ok()
6807                .and_then(|o| String::from_utf8(o.stdout).ok())
6808                .map(|s| s.trim() == "1")
6809                .unwrap_or(false);
6810
6811            let reduce_transparency = std::process::Command::new("defaults")
6812                .args([
6813                    "read",
6814                    "-g",
6815                    "com.apple.universalaccess",
6816                    "reduceTransparency",
6817                ])
6818                .output()
6819                .ok()
6820                .and_then(|o| String::from_utf8(o.stdout).ok())
6821                .map(|s| s.trim() == "1")
6822                .unwrap_or(false);
6823
6824            let increase_contrast = std::process::Command::new("defaults")
6825                .args([
6826                    "read",
6827                    "-g",
6828                    "com.apple.universalaccess",
6829                    "increaseContrast",
6830                ])
6831                .output()
6832                .ok()
6833                .and_then(|o| String::from_utf8(o.stdout).ok())
6834                .map(|s| s.trim() == "1")
6835                .unwrap_or(false);
6836
6837            Self {
6838                reduce_motion,
6839                reduce_transparency,
6840                increase_contrast,
6841            }
6842        }
6843
6844        #[cfg(target_os = "linux")]
6845        {
6846            // Reduced motion: check GTK_A11Y env var or GNOME gsettings
6847            let reduce_motion = std::env::var("GTK_A11Y")
6848                .map(|v| v.to_lowercase().contains("reduce-motion"))
6849                .unwrap_or(false)
6850                || {
6851                    // Try gsettings for GNOME desktop animation preference
6852                    std::process::Command::new("gsettings")
6853                        .args([
6854                            "get",
6855                            "org.gnome.desktop.interface",
6856                            "enable-animations",
6857                        ])
6858                        .output()
6859                        .ok()
6860                        .and_then(|o| String::from_utf8(o.stdout).ok())
6861                        .map(|s| s.trim() == "'false'" || s.trim() == "false")
6862                        .unwrap_or(false)
6863                };
6864
6865            // Reduced transparency is not widely supported on Linux desktops
6866            let reduce_transparency = false;
6867
6868            // Increased contrast: check GTK_THEME for high-contrast variants
6869            let increase_contrast = std::env::var("GTK_THEME")
6870                .map(|v| v.to_lowercase().contains("highcontrast"))
6871                .unwrap_or(false);
6872
6873            Self {
6874                reduce_motion,
6875                reduce_transparency,
6876                increase_contrast,
6877            }
6878        }
6879
6880        #[cfg(target_os = "windows")]
6881        {
6882            use std::process::Command;
6883
6884            // Helper: run `reg query` and return the value string if found
6885            fn reg_query(key: &str, value_name: &str) -> Option<String> {
6886                Command::new("reg")
6887                    .args(["query", key, "/v", value_name])
6888                    .output()
6889                    .ok()
6890                    .and_then(|o| {
6891                        if o.status.success() {
6892                            String::from_utf8(o.stdout).ok()
6893                        } else {
6894                            None
6895                        }
6896                    })
6897                    .and_then(|s| {
6898                        // Output format: "    ValueName    REG_SZ    <value>"
6899                        // or REG_DWORD lines; parse the last token on the last non-empty line
6900                        s.lines()
6901                            .last()?
6902                            .split_whitespace()
6903                            .last()
6904                            .map(String::from)
6905                    })
6906            }
6907
6908            // Reduced motion: EffectsAnimationEfficiency = 1 means reduced
6909            let reduce_motion = reg_query(
6910                "HKCU\\Control Panel\\Accessibility\\EffectsAnimationEfficiency",
6911                "EffectsAnimationEfficiency",
6912            )
6913            .map(|v| v == "1")
6914            .unwrap_or(false);
6915
6916            // Reduced transparency: EnableTransparency = 0 means reduced
6917            let reduce_transparency = reg_query(
6918                "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
6919                "EnableTransparency",
6920            )
6921            .map(|v| v == "0")
6922            .unwrap_or(false);
6923
6924            // Increased contrast: HighContrast = 1 means enabled
6925            let increase_contrast = reg_query(
6926                "HKCU\\Control Panel\\Accessibility\\HighContrast",
6927                "HighContrast",
6928            )
6929            .map(|v| v == "1")
6930            .unwrap_or(false);
6931
6932            Self {
6933                reduce_motion,
6934                reduce_transparency,
6935                increase_contrast,
6936            }
6937        }
6938
6939        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
6940        {
6941            Self::default()
6942        }
6943    }
6944
6945    /// Apply a minimum alpha constraint for increase-contrast mode.
6946    pub fn min_alpha(&self, requested: f32) -> f32 {
6947        if self.increase_contrast {
6948            requested.max(0.5)
6949        } else {
6950            requested
6951        }
6952    }
6953
6954    /// Returns true if glass effects should be replaced with opaque surfaces.
6955    pub fn should_disable_glass(&self) -> bool {
6956        self.reduce_transparency
6957    }
6958
6959    /// Returns true if animations should be instant.
6960    pub fn should_reduce_motion(&self) -> bool {
6961        self.reduce_motion
6962    }
6963
6964    /// Returns true if borders should be made visible.
6965    pub fn should_increase_contrast(&self) -> bool {
6966        self.increase_contrast
6967    }
6968}
6969
6970/// Get the current accessibility preferences for this thread.
6971pub fn accessibility_preferences() -> AccessibilityPreferences {
6972    ACCESSIBILITY_PREFS.with(|p| *p.borrow())
6973}
6974
6975/// Set the accessibility preferences for this thread.
6976///
6977/// The native renderer should call this on startup and when system
6978/// preferences change (via `detect_from_system()`).
6979pub fn set_accessibility_preferences(prefs: AccessibilityPreferences) {
6980    ACCESSIBILITY_PREFS.with(|p| {
6981        *p.borrow_mut() = prefs;
6982    });
6983}
6984
6985// =============================================================================
6986// CLIPBOARD -- System clipboard access
6987// =============================================================================
6988
6989/// Trait for clipboard operations.
6990///
6991/// The native renderer implements this via `arboard` on desktop platforms.
6992/// On WASM, it uses the browser Clipboard API.
6993pub trait ClipboardProvider: Send + Sync {
6994    /// Read text from the system clipboard.
6995    fn read_text(&self) -> Option<String>;
6996    /// Write text to the system clipboard.
6997    fn write_text(&self, text: &str);
6998}
6999
7000/// Default clipboard implementation using `arboard`.
7001/// Note: This is only available when the `arboard` feature is enabled.
7002/// The renderer provides the concrete implementation.
7003#[cfg(all(not(target_arch = "wasm32"), target_os = "macos"))]
7004pub struct SystemClipboard;
7005
7006#[cfg(all(not(target_arch = "wasm32"), target_os = "macos"))]
7007impl ClipboardProvider for SystemClipboard {
7008    fn read_text(&self) -> Option<String> {
7009        use std::process::Command;
7010        // Fallback: try pbpaste on macOS
7011        Command::new("pbpaste")
7012            .output()
7013            .ok()
7014            .and_then(|o| String::from_utf8(o.stdout).ok())
7015    }
7016
7017    fn write_text(&self, text: &str) {
7018        use std::process::Command;
7019        // Fallback: try pbcopy on macOS
7020        if let Ok(mut child) = Command::new("pbcopy")
7021            .stdin(std::process::Stdio::piped())
7022            .spawn()
7023        {
7024            if let Some(stdin) = child.stdin.as_mut() {
7025                use std::io::Write;
7026                let _ = stdin.write_all(text.as_bytes());
7027            }
7028            let _ = child.wait();
7029        }
7030    }
7031}
7032
7033// =============================================================================
7034// TEXT INPUT -- Direction enum for cursor movement
7035// =============================================================================
7036
7037/// Direction for cursor movement in text input.
7038#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7039pub enum TextDirection {
7040    Forward,
7041    Backward,
7042    Up,
7043    Down,
7044    LineStart,
7045    LineEnd,
7046    WordForward,
7047    WordBackward,
7048}
7049
7050/// Text input state managed by the renderer.
7051///
7052/// Components don't store this directly -- the renderer maintains it
7053/// and components query/modify it through the Renderer trait methods.
7054#[derive(Debug, Clone, Default)]
7055pub struct TextInputState {
7056    /// The full text content.
7057    pub text: String,
7058    /// Cursor position as byte offset into the text.
7059    pub cursor_pos: usize,
7060    /// Selection anchor. If Some, the selection is from anchor to cursor.
7061    /// If None, there is no selection.
7062    pub selection_anchor: Option<usize>,
7063    /// Whether the input is focused (shows cursor, accepts keyboard).
7064    pub focused: bool,
7065    /// Whether the caret is currently visible (for blinking).
7066    pub caret_visible: bool,
7067    /// Last edit timestamp for undo coalescing.
7068    pub last_edit_time: f32,
7069}
7070
7071impl TextInputState {
7072    /// Create a new TextInputState with the given initial text.
7073    pub fn new(text: impl Into<String>) -> Self {
7074        let text = text.into();
7075        let cursor_pos = text.len();
7076        Self {
7077            text,
7078            cursor_pos,
7079            selection_anchor: None,
7080            focused: false,
7081            caret_visible: true,
7082            last_edit_time: 0.0,
7083        }
7084    }
7085
7086    /// Get the selection range as (start, end) byte offsets.
7087    /// Returns None if there is no selection.
7088    pub fn selection_range(&self) -> Option<(usize, usize)> {
7089        self.selection_anchor.map(|anchor| {
7090            if anchor <= self.cursor_pos {
7091                (anchor, self.cursor_pos)
7092            } else {
7093                (self.cursor_pos, anchor)
7094            }
7095        })
7096    }
7097
7098    /// Get the selected text, or empty string if no selection.
7099    pub fn selected_text(&self) -> String {
7100        self.selection_range()
7101            .map(|(start, end)| self.text[start..end].to_string())
7102            .unwrap_or_default()
7103    }
7104
7105    /// Insert text at the current cursor position, replacing any selection.
7106    pub fn insert(&mut self, new_text: &str) {
7107        if let Some((start, end)) = self.selection_range() {
7108            self.text.replace_range(start..end, new_text);
7109            self.cursor_pos = start + new_text.len();
7110        } else {
7111            self.text.insert_str(self.cursor_pos, new_text);
7112            self.cursor_pos += new_text.len();
7113        }
7114        self.selection_anchor = None;
7115    }
7116
7117    /// Delete characters. If there's a selection, delete it.
7118    /// Otherwise delete `count` characters backward (backspace) or forward (delete).
7119    pub fn delete(&mut self, backward: bool, count: usize) -> String {
7120        if let Some((start, end)) = self.selection_range() {
7121            let deleted = self.text[start..end].to_string();
7122            self.text.replace_range(start..end, "");
7123            self.cursor_pos = start;
7124            self.selection_anchor = None;
7125            return deleted;
7126        }
7127
7128        if backward && self.cursor_pos > 0 {
7129            let start = self.cursor_pos.saturating_sub(count);
7130            let deleted = self.text[start..self.cursor_pos].to_string();
7131            self.text.replace_range(start..self.cursor_pos, "");
7132            self.cursor_pos = start;
7133            deleted
7134        } else if !backward && self.cursor_pos < self.text.len() {
7135            let end = (self.cursor_pos + count).min(self.text.len());
7136            let deleted = self.text[self.cursor_pos..end].to_string();
7137            self.text.replace_range(self.cursor_pos..end, "");
7138            deleted
7139        } else {
7140            String::new()
7141        }
7142    }
7143
7144    /// Move the cursor in the given direction.
7145    pub fn move_cursor(&mut self, direction: TextDirection, extend_selection: bool) {
7146        if !extend_selection {
7147            self.selection_anchor = None;
7148        } else if self.selection_anchor.is_none() {
7149            self.selection_anchor = Some(self.cursor_pos);
7150        }
7151
7152        match direction {
7153            TextDirection::Forward if self.cursor_pos < self.text.len() => {
7154                // Move to next character boundary (UTF-8 safe)
7155                let next = self.text[self.cursor_pos..]
7156                    .char_indices()
7157                    .nth(1)
7158                    .map(|(i, _)| self.cursor_pos + i)
7159                    .unwrap_or(self.text.len());
7160                self.cursor_pos = next;
7161            }
7162            TextDirection::Backward if self.cursor_pos > 0 => {
7163                let prev = self.text[..self.cursor_pos]
7164                    .char_indices()
7165                    .next_back()
7166                    .map(|(i, _)| i)
7167                    .unwrap_or(0);
7168                self.cursor_pos = prev;
7169            }
7170            TextDirection::LineStart => {
7171                self.cursor_pos = 0;
7172            }
7173            TextDirection::LineEnd => {
7174                self.cursor_pos = self.text.len();
7175            }
7176            TextDirection::WordForward => {
7177                // Find next word boundary
7178                let rest = &self.text[self.cursor_pos..];
7179                // Skip current word chars
7180                let after_word = rest
7181                    .char_indices()
7182                    .find(|(_, c)| !c.is_alphanumeric())
7183                    .map(|(i, _)| i)
7184                    .unwrap_or(rest.len());
7185                // Skip whitespace
7186                let after_space = rest[after_word..]
7187                    .char_indices()
7188                    .find(|(_, c)| !c.is_whitespace())
7189                    .map(|(i, _)| after_word + i)
7190                    .unwrap_or(rest.len());
7191                self.cursor_pos = (self.cursor_pos + after_space).min(self.text.len());
7192            }
7193            TextDirection::WordBackward => {
7194                let before = &self.text[..self.cursor_pos];
7195                // Skip whitespace going backward
7196                let before_word = before
7197                    .char_indices()
7198                    .rev()
7199                    .find(|(_, c)| !c.is_whitespace())
7200                    .map(|(i, _)| i)
7201                    .unwrap_or(0);
7202                // Skip word chars going backward
7203                let word_start = before[..before_word]
7204                    .char_indices()
7205                    .rev()
7206                    .find(|(_, c)| !c.is_alphanumeric())
7207                    .map(|(i, _)| i)
7208                    .unwrap_or(0);
7209                self.cursor_pos = word_start;
7210            }
7211            _ => {} // Up/Down handled by multi-line components
7212        }
7213
7214        if !extend_selection {
7215            self.selection_anchor = None;
7216        }
7217    }
7218
7219    /// Select all text.
7220    pub fn select_all(&mut self) {
7221        self.cursor_pos = self.text.len();
7222        self.selection_anchor = Some(0);
7223    }
7224
7225    /// Get the byte offset of the cursor.
7226    pub fn cursor_byte_pos(&self) -> usize {
7227        self.cursor_pos
7228    }
7229}
7230
7231/// Action details for interactive buttons inside a notification.
7232#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
7233pub struct NotificationAction {
7234    /// Unique identifier of the action.
7235    pub id: String,
7236    /// The text label to display on the action button.
7237    pub title: String,
7238    /// Indicates whether the action performs a destructive task (e.g. Delete).
7239    pub is_destructive: bool,
7240}
7241
7242/// Priority tier of a notification.
7243#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
7244pub enum NotificationPriority {
7245    /// Placed silently into the notification center without visual alerts.
7246    Passive,
7247    /// Triggers a visual alert (toast) but does not interrupt focus.
7248    #[default]
7249    Active,
7250    /// Important alert that bypasses standard DND/Focus bounds.
7251    TimeSensitive,
7252}
7253
7254/// A structured notification representation.
7255#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
7256pub struct Notification {
7257    /// Unique identifier for this notification.
7258    pub id: String,
7259    /// App or source identifier spawning this notification.
7260    pub app_name: Option<String>,
7261    /// The bold heading/title text.
7262    pub title: String,
7263    /// The detailed descriptive body text.
7264    pub body: String,
7265    /// Optional URI or path to an icon asset.
7266    pub icon: Option<String>,
7267    /// Optional sound identifier to play when posting.
7268    pub sound: Option<String>,
7269    /// Interactive actions available on this notification.
7270    pub actions: Vec<NotificationAction>,
7271    /// Timer duration in seconds after which the toast auto-dismisses.
7272    pub timeout: Option<f32>,
7273    /// Priority level for delivery logic.
7274    pub priority: NotificationPriority,
7275    /// Time (in seconds since renderer startup) when this notification was posted.
7276    pub timestamp: f32,
7277    /// Whether the notification has been dismissed/read.
7278    pub dismissed: bool,
7279}
7280
7281/// Error type indicating a failure in generating or posting a notification.
7282#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, thiserror::Error)]
7283pub enum NotificationError {
7284    /// Permissions denied.
7285    #[error("Notification permission denied")]
7286    PermissionDenied,
7287    /// Failed to post the notification.
7288    #[error("Failed to post notification")]
7289    PostFailed,
7290}
7291
7292/// State of notification permissions.
7293#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
7294pub enum NotificationPermission {
7295    /// Explicitly allowed.
7296    Granted,
7297    /// Explicitly blocked.
7298    Denied,
7299    /// Prompt has not been shown or decided yet.
7300    #[default]
7301    NotDetermined,
7302}
7303
7304/// Core interface for routing and dispatching notification events.
7305pub trait NotificationHandler: Send + Sync {
7306    /// Posts a new notification.
7307    fn show(&self, notification: Notification) -> Result<(), NotificationError>;
7308    /// Dismisses a notification by ID.
7309    fn dismiss(&self, id: &str) -> Result<(), NotificationError>;
7310    /// Requests delivery permission.
7311    fn request_permission(&self) -> NotificationPermission;
7312}
7313
7314static NEXT_NOTIFICATION_ID: std::sync::atomic::AtomicUsize =
7315    std::sync::atomic::AtomicUsize::new(1);
7316
7317/// Default in-app notification handler that writes state to AppState.
7318#[derive(Clone, Copy, Debug, Default)]
7319pub struct DefaultNotificationHandler;
7320
7321impl NotificationHandler for DefaultNotificationHandler {
7322    /// Save the notification to the global system state (history) and auto-assign an ID if empty.
7323    fn show(&self, notification: Notification) -> Result<(), NotificationError> {
7324        let mut notif = notification;
7325        if notif.id.is_empty() {
7326            let id = NEXT_NOTIFICATION_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
7327            notif.id = format!("notif_{}", id);
7328        }
7329        update_system_state(|state| {
7330            let mut new_state = state.clone();
7331            new_state.notifications.push(notif.clone());
7332            new_state
7333        });
7334        Ok(())
7335    }
7336
7337    /// Mark a notification as dismissed/read in the global system state.
7338    fn dismiss(&self, id: &str) -> Result<(), NotificationError> {
7339        update_system_state(|state| {
7340            let mut new_state = state.clone();
7341            for notif in &mut new_state.notifications {
7342                if notif.id == id {
7343                    notif.dismissed = true;
7344                }
7345            }
7346            new_state
7347        });
7348        Ok(())
7349    }
7350
7351    /// Returns the permission state (always Granted for internal in-app notifications).
7352    fn request_permission(&self) -> NotificationPermission {
7353        NotificationPermission::Granted
7354    }
7355}
7356
7357static NOTIFICATION_HANDLER: once_cell::sync::OnceCell<std::sync::Arc<dyn NotificationHandler>> =
7358    once_cell::sync::OnceCell::new();
7359
7360/// Sets the global notification handler.
7361pub fn set_notification_handler(handler: std::sync::Arc<dyn NotificationHandler>) {
7362    let _ = NOTIFICATION_HANDLER.set(handler);
7363}
7364
7365/// Gets the global notification handler, fallback to DefaultNotificationHandler.
7366pub fn get_notification_handler() -> std::sync::Arc<dyn NotificationHandler> {
7367    NOTIFICATION_HANDLER
7368        .get_or_init(|| std::sync::Arc::new(DefaultNotificationHandler))
7369        .clone()
7370}
7371
7372/// Filter mapping name to extension list for a file dialog.
7373#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
7374pub struct FileFilter {
7375    /// Friendly name of the filter (e.g. "Images").
7376    pub name: String,
7377    /// List of file extensions (e.g. ["png", "jpg"]).
7378    pub extensions: Vec<String>,
7379}
7380
7381/// The mode/purpose of the file dialog.
7382#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
7383pub enum FileDialogMode {
7384    /// Pick a single or multiple files to open.
7385    #[default]
7386    OpenFile,
7387    /// Pick a directory path.
7388    OpenDirectory,
7389    /// Prompt for a location/name to save a file.
7390    SaveFile,
7391}
7392
7393/// Dialog options for picking files or directories.
7394#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
7395pub struct FileDialog {
7396    /// Title displayed in the dialog window.
7397    pub title: String,
7398    /// Optional starting directory path.
7399    pub default_path: Option<String>,
7400    /// Extensions used to filter selection.
7401    pub filters: Vec<FileFilter>,
7402    /// Open/save mode.
7403    pub mode: FileDialogMode,
7404    /// Allows selecting multiple files if in OpenFile mode.
7405    pub allow_multiple: bool,
7406}
7407
7408/// Errors returned by the file dialog.
7409#[derive(Debug, thiserror::Error)]
7410pub enum FileDialogError {
7411    /// The user closed the dialog without selecting anything.
7412    #[error("File dialog cancelled")]
7413    Cancelled,
7414    /// An input/output error occurred.
7415    #[error("I/O error: {0}")]
7416    Io(#[from] std::io::Error),
7417    /// Platform-specific error.
7418    #[error("Platform error: {0}")]
7419    Platform(String),
7420}
7421
7422impl FileDialog {
7423    /// Creates a new FileDialog with the given mode.
7424    pub fn new(mode: FileDialogMode) -> Self {
7425        Self {
7426            mode,
7427            ..Default::default()
7428        }
7429    }
7430
7431    /// Sets the dialog title.
7432    pub fn title(mut self, title: impl Into<String>) -> Self {
7433        self.title = title.into();
7434        self
7435    }
7436
7437    /// Adds a file filter.
7438    pub fn add_filter(mut self, name: &str, extensions: &[&str]) -> Self {
7439        self.filters.push(FileFilter {
7440            name: name.to_string(),
7441            extensions: extensions.iter().map(|s| s.to_string()).collect(),
7442        });
7443        self
7444    }
7445
7446    /// Sets the default starting directory path.
7447    pub fn default_path(mut self, path: impl Into<String>) -> Self {
7448        self.default_path = Some(path.into());
7449        self
7450    }
7451
7452    /// Sets whether selecting multiple files is allowed.
7453    pub fn allow_multiple(mut self, allow: bool) -> Self {
7454        self.allow_multiple = allow;
7455        self
7456    }
7457}
7458
7459#[cfg(not(target_arch = "wasm32"))]
7460impl FileDialog {
7461    /// Pick file(s) or folder based on current mode configuration.
7462    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
7463        let mut dialog = rfd::FileDialog::new();
7464        dialog = dialog.set_title(&self.title);
7465        if let Some(path) = &self.default_path {
7466            dialog = dialog.set_directory(path);
7467        }
7468        for filter in &self.filters {
7469            let refs: Vec<&str> = filter.extensions.iter().map(|s| s.as_str()).collect();
7470            dialog = dialog.add_filter(&filter.name, &refs);
7471        }
7472
7473        match self.mode {
7474            FileDialogMode::OpenFile => {
7475                if self.allow_multiple {
7476                    dialog.pick_files().ok_or(FileDialogError::Cancelled)
7477                } else {
7478                    Ok(dialog.pick_file().into_iter().collect())
7479                }
7480            }
7481            FileDialogMode::OpenDirectory => Ok(dialog.pick_folder().into_iter().collect()),
7482            FileDialogMode::SaveFile => Ok(dialog.save_file().into_iter().collect()),
7483        }
7484    }
7485
7486    /// Helper to pick a single file/directory, returning None if cancelled.
7487    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
7488        let results = self.pick()?;
7489        Ok(results.into_iter().next())
7490    }
7491}
7492
7493#[cfg(target_arch = "wasm32")]
7494impl FileDialog {
7495    /// Pick is unsupported/mocked on WASM.
7496    pub fn pick(self) -> Result<Vec<std::path::PathBuf>, FileDialogError> {
7497        Err(FileDialogError::Platform(
7498            "FileDialog is not supported synchronously on WebAssembly".to_string(),
7499        ))
7500    }
7501
7502    /// Helper to pick a single file/directory, returning None if cancelled.
7503    pub fn pick_single(self) -> Result<Option<std::path::PathBuf>, FileDialogError> {
7504        Err(FileDialogError::Platform(
7505            "FileDialog is not supported synchronously on WebAssembly".to_string(),
7506        ))
7507    }
7508}
7509
7510/// Error type representing a failure in Document load/save/parse operations.
7511#[derive(Debug, thiserror::Error)]
7512pub enum DocumentError {
7513    /// An input/output error occurred.
7514    #[error("I/O error: {0}")]
7515    Io(#[from] std::io::Error),
7516    /// Failure during deserialization or parsing.
7517    #[error("Parse error: {0}")]
7518    Parse(String),
7519    /// Failure during serialization.
7520    #[error("Serialization error: {0}")]
7521    Serialize(String),
7522}
7523
7524/// A document interface mapping to local filesystem persistence.
7525pub trait Document: Send + Sync {
7526    /// Loads the document from the specified path.
7527    fn read_from(path: &std::path::Path) -> Result<Self, DocumentError>
7528    where
7529        Self: Sized;
7530
7531    /// Saves the document to the specified path.
7532    fn write_to(&self, path: &std::path::Path) -> Result<(), DocumentError>;
7533
7534    /// Returns true if the document has unsaved modifications.
7535    fn is_dirty(&self) -> bool;
7536
7537    /// Marks the document as clean/saved.
7538    fn mark_clean(&mut self);
7539}
7540
7541/// Periodic auto-save coordinator for open Documents.
7542pub struct AutoSaveManager {
7543    /// Time interval in seconds between auto-saves.
7544    pub interval: f32,
7545    /// Elapsed timer tracker.
7546    pub timer: f32,
7547    /// Registered open documents under management.
7548    pub documents: Vec<(std::path::PathBuf, Box<dyn Document>)>,
7549}
7550
7551impl AutoSaveManager {
7552    /// Creates a new AutoSaveManager with the specified check interval.
7553    pub fn new(interval: f32) -> Self {
7554        Self {
7555            interval,
7556            timer: 0.0,
7557            documents: Vec::new(),
7558        }
7559    }
7560
7561    /// Register a document with its current file path.
7562    pub fn register(&mut self, path: std::path::PathBuf, doc: Box<dyn Document>) {
7563        self.documents.push((path, doc));
7564    }
7565
7566    /// Advance the timer and auto-save any dirty documents when the interval is reached.
7567    pub fn tick(&mut self, dt: f32) {
7568        self.timer += dt;
7569        if self.timer >= self.interval {
7570            self.timer = 0.0;
7571            for (path, doc) in &mut self.documents {
7572                if doc.is_dirty() {
7573                    match doc.write_to(path) {
7574                        Ok(()) => {
7575                            doc.mark_clean();
7576                            log::info!("[AutoSaveManager] Auto-saved document to {:?}", path);
7577                        }
7578                        Err(e) => {
7579                            log::error!(
7580                                "[AutoSaveManager] Failed to auto-save document to {:?}: {:?}",
7581                                path,
7582                                e
7583                            );
7584                        }
7585                    }
7586                }
7587            }
7588        }
7589    }
7590}
7591
7592// ── Menu Bar API ──────────────────────────────────────────────────────────────
7593
7594/// Keyboard modifier flags used by [`KeyboardShortcut`].
7595///
7596/// On macOS, `cmd` maps to the Command (⌘) key.
7597/// On all other platforms, `cmd` maps to the Control key.
7598/// This is enforced at the renderer level, not here; the data model is OS-agnostic.
7599#[derive(Debug, Clone, PartialEq, Eq, Default)]
7600pub struct Modifiers {
7601    /// Command on macOS, Control on Windows/Linux.
7602    pub cmd: bool,
7603    /// Shift key.
7604    pub shift: bool,
7605    /// Alt/Option key.
7606    pub alt: bool,
7607    /// Control key (distinct from cmd on all platforms).
7608    pub ctrl: bool,
7609}
7610
7611/// A keyboard shortcut binding to a menu action.
7612#[derive(Debug, Clone)]
7613pub struct KeyboardShortcut {
7614    /// The key character or name, e.g. `"s"`, `"z"`, `"Return"`.
7615    pub key: String,
7616    /// The required modifier combination.
7617    pub modifiers: Modifiers,
7618}
7619
7620impl KeyboardShortcut {
7621    /// Convenience constructor: cmd (or ctrl on non-macOS) + `key`.
7622    pub fn cmd(key: impl Into<String>) -> Self {
7623        Self {
7624            key: key.into(),
7625            modifiers: Modifiers {
7626                cmd: true,
7627                ..Default::default()
7628            },
7629        }
7630    }
7631
7632    /// Convenience constructor: cmd+Shift + `key`.
7633    pub fn cmd_shift(key: impl Into<String>) -> Self {
7634        Self {
7635            key: key.into(),
7636            modifiers: Modifiers {
7637                cmd: true,
7638                shift: true,
7639                ..Default::default()
7640            },
7641        }
7642    }
7643}
7644
7645/// A single entry in a [`MenuBar`].
7646///
7647/// Actions hold a callback that is invoked when the user activates the item
7648/// (either via the menu UI or via the associated keyboard shortcut).
7649/// Separators provide visual grouping. Submenus allow hierarchical menus.
7650pub enum MenuItem {
7651    /// An activatable menu entry with an optional shortcut and enabled/disabled state.
7652    Action {
7653        label: String,
7654        shortcut: Option<KeyboardShortcut>,
7655        action: std::sync::Arc<dyn Fn() + Send + Sync>,
7656        enabled: bool,
7657    },
7658    /// A nested submenu.
7659    Submenu { label: String, items: Vec<MenuItem> },
7660    /// A visual separator line between groups of items.
7661    Separator,
7662}
7663
7664impl std::fmt::Debug for MenuItem {
7665    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7666        match self {
7667            Self::Action { label, enabled, .. } => f
7668                .debug_struct("Action")
7669                .field("label", label)
7670                .field("enabled", enabled)
7671                .finish(),
7672            Self::Submenu { label, items } => f
7673                .debug_struct("Submenu")
7674                .field("label", label)
7675                .field("items", items)
7676                .finish(),
7677            Self::Separator => write!(f, "Separator"),
7678        }
7679    }
7680}
7681
7682/// A top-level menu bar containing [`MenuItem`]s.
7683///
7684/// The menu bar is a data model only; rendering it into an OS-native menu is
7685/// handled by the platform renderer (`cvkg-render-native`).
7686pub struct MenuBar {
7687    /// Ordered list of top-level menu items.
7688    pub items: Vec<MenuItem>,
7689}
7690
7691impl MenuBar {
7692    /// Create an empty menu bar.
7693    pub fn new() -> Self {
7694        Self { items: Vec::new() }
7695    }
7696
7697    /// Append a menu item to the bar.
7698    pub fn add_item(&mut self, item: MenuItem) {
7699        self.items.push(item);
7700    }
7701
7702    /// Build the standard CVKG menu structure with all conventional shortcuts.
7703    ///
7704    /// The `cmd` modifier maps to ⌘ on macOS and Ctrl on Windows/Linux -- this
7705    /// translation is enforced by the renderer, not here.
7706    ///
7707    /// Menus included:
7708    /// - **File**: New, Open, Save, Close
7709    /// - **Edit**: Undo, Redo, Cut, Copy, Paste, Select All, Find
7710    /// - **View**: Zoom In, Zoom Out, Fullscreen
7711    /// - **Window**: Minimize, Zoom, Bring All to Front
7712    /// - **Help**: Search Help
7713    #[allow(clippy::too_many_arguments)]
7714    pub fn standard(
7715        new_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7716        open_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7717        save_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7718        close_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7719        quit_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7720        undo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7721        redo_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7722        cut_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7723        copy_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7724        paste_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7725        select_all_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7726        find_fn: std::sync::Arc<dyn Fn() + Send + Sync>,
7727    ) -> Self {
7728        let mut bar = Self::new();
7729
7730        // ── File ──────────────────────────────────────────────────────────────
7731        bar.add_item(MenuItem::Submenu {
7732            label: "File".to_string(),
7733            items: vec![
7734                MenuItem::Action {
7735                    label: "New".to_string(),
7736                    shortcut: Some(KeyboardShortcut::cmd("n")),
7737                    action: new_fn,
7738                    enabled: true,
7739                },
7740                MenuItem::Action {
7741                    label: "Open…".to_string(),
7742                    shortcut: Some(KeyboardShortcut::cmd("o")),
7743                    action: open_fn,
7744                    enabled: true,
7745                },
7746                MenuItem::Separator,
7747                MenuItem::Action {
7748                    label: "Save".to_string(),
7749                    shortcut: Some(KeyboardShortcut::cmd("s")),
7750                    action: save_fn,
7751                    enabled: true,
7752                },
7753                MenuItem::Separator,
7754                MenuItem::Action {
7755                    label: "Close".to_string(),
7756                    shortcut: Some(KeyboardShortcut::cmd("w")),
7757                    action: close_fn,
7758                    enabled: true,
7759                },
7760                MenuItem::Separator,
7761                MenuItem::Action {
7762                    label: "Quit".to_string(),
7763                    shortcut: Some(KeyboardShortcut::cmd("q")),
7764                    action: quit_fn,
7765                    enabled: true,
7766                },
7767            ],
7768        });
7769
7770        // ── Edit ──────────────────────────────────────────────────────────────
7771        bar.add_item(MenuItem::Submenu {
7772            label: "Edit".to_string(),
7773            items: vec![
7774                MenuItem::Action {
7775                    label: "Undo".to_string(),
7776                    shortcut: Some(KeyboardShortcut::cmd("z")),
7777                    action: undo_fn,
7778                    enabled: true,
7779                },
7780                MenuItem::Action {
7781                    label: "Redo".to_string(),
7782                    shortcut: Some(KeyboardShortcut::cmd_shift("z")),
7783                    action: redo_fn,
7784                    enabled: true,
7785                },
7786                MenuItem::Separator,
7787                MenuItem::Action {
7788                    label: "Cut".to_string(),
7789                    shortcut: Some(KeyboardShortcut::cmd("x")),
7790                    action: cut_fn,
7791                    enabled: true,
7792                },
7793                MenuItem::Action {
7794                    label: "Copy".to_string(),
7795                    shortcut: Some(KeyboardShortcut::cmd("c")),
7796                    action: copy_fn,
7797                    enabled: true,
7798                },
7799                MenuItem::Action {
7800                    label: "Paste".to_string(),
7801                    shortcut: Some(KeyboardShortcut::cmd("v")),
7802                    action: paste_fn,
7803                    enabled: true,
7804                },
7805                MenuItem::Separator,
7806                MenuItem::Action {
7807                    label: "Select All".to_string(),
7808                    shortcut: Some(KeyboardShortcut::cmd("a")),
7809                    action: select_all_fn,
7810                    enabled: true,
7811                },
7812                MenuItem::Separator,
7813                MenuItem::Action {
7814                    label: "Find…".to_string(),
7815                    shortcut: Some(KeyboardShortcut::cmd("f")),
7816                    action: find_fn,
7817                    enabled: true,
7818                },
7819            ],
7820        });
7821
7822        // ── View ──────────────────────────────────────────────────────────────
7823        // View items carry no application-level callbacks at the model layer;
7824        // zoom and fullscreen are handled by the renderer directly.
7825        let noop: std::sync::Arc<dyn Fn() + Send + Sync> = std::sync::Arc::new(|| {});
7826        bar.add_item(MenuItem::Submenu {
7827            label: "View".to_string(),
7828            items: vec![
7829                MenuItem::Action {
7830                    label: "Zoom In".to_string(),
7831                    shortcut: Some(KeyboardShortcut::cmd("=")),
7832                    action: noop.clone(),
7833                    enabled: true,
7834                },
7835                MenuItem::Action {
7836                    label: "Zoom Out".to_string(),
7837                    shortcut: Some(KeyboardShortcut::cmd("-")),
7838                    action: noop.clone(),
7839                    enabled: true,
7840                },
7841                MenuItem::Separator,
7842                MenuItem::Action {
7843                    label: "Toggle Fullscreen".to_string(),
7844                    shortcut: Some(KeyboardShortcut {
7845                        key: "f".to_string(),
7846                        modifiers: Modifiers {
7847                            ctrl: true,
7848                            ..Default::default()
7849                        },
7850                    }),
7851                    action: noop.clone(),
7852                    enabled: true,
7853                },
7854            ],
7855        });
7856
7857        // ── Window ────────────────────────────────────────────────────────────
7858        bar.add_item(MenuItem::Submenu {
7859            label: "Window".to_string(),
7860            items: vec![
7861                MenuItem::Action {
7862                    label: "Minimize".to_string(),
7863                    shortcut: Some(KeyboardShortcut::cmd("m")),
7864                    action: noop.clone(),
7865                    enabled: true,
7866                },
7867                MenuItem::Action {
7868                    label: "Zoom".to_string(),
7869                    shortcut: None,
7870                    action: noop.clone(),
7871                    enabled: true,
7872                },
7873                MenuItem::Separator,
7874                MenuItem::Action {
7875                    label: "Bring All to Front".to_string(),
7876                    shortcut: None,
7877                    action: noop.clone(),
7878                    enabled: true,
7879                },
7880            ],
7881        });
7882
7883        // ── Help ──────────────────────────────────────────────────────────────
7884        bar.add_item(MenuItem::Submenu {
7885            label: "Help".to_string(),
7886            items: vec![MenuItem::Action {
7887                label: "Search Help".to_string(),
7888                shortcut: None,
7889                action: noop,
7890                enabled: true,
7891            }],
7892        });
7893
7894        bar
7895    }
7896}
7897
7898impl Default for MenuBar {
7899    fn default() -> Self {
7900        Self::new()
7901    }
7902}
7903
7904// =============================================================================
7905// LOCALIZATION -- Item 12: Localization / Internationalization
7906// =============================================================================
7907// OS-agnostic: works on all platforms. No platform-specific string loading.
7908
7909use std::sync::RwLock;
7910
7911/// Layout direction for UI elements (LTR or RTL).
7912#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
7913pub enum Direction {
7914    #[default]
7915    LTR,
7916    RTL,
7917    Auto,
7918}
7919
7920impl Direction {
7921    pub fn is_rtl(self) -> bool {
7922        matches!(self, Direction::RTL)
7923    }
7924}
7925#[derive(Clone, Debug)]
7926pub struct L10nBundle {
7927    pub locale: String,
7928    pub strings: HashMap<String, String>,
7929    pub is_rtl: bool,
7930}
7931
7932impl L10nBundle {
7933    pub fn new(locale: impl Into<String>) -> Self {
7934        Self {
7935            locale: locale.into(),
7936            strings: HashMap::new(),
7937            is_rtl: false,
7938        }
7939    }
7940
7941    pub fn add(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
7942        self.strings.insert(key.into(), value.into());
7943        self
7944    }
7945
7946    pub fn from_strings_format(locale: impl Into<String>, input: &str) -> Self {
7947        let mut bundle = Self::new(locale);
7948        for line in input.lines() {
7949            let line = line.trim();
7950            if line.is_empty() || line.starts_with("//") {
7951                continue;
7952            }
7953            if let Some(eq_pos) = line.find(" = ") {
7954                let key = line[..eq_pos].trim_matches('"').to_string();
7955                let val = line[eq_pos + 3..]
7956                    .trim_end_matches(';')
7957                    .trim_matches('"')
7958                    .to_string();
7959                bundle.strings.insert(key, val);
7960            }
7961        }
7962        bundle
7963    }
7964    /// Get a translated string by key. Returns the key itself if not found.
7965    pub fn t(&self, key: &str) -> String {
7966        self.strings
7967            .get(key)
7968            .map(|s| s.to_string())
7969            .unwrap_or_else(|| key.to_string())
7970    }
7971
7972    /// Translate with interpolation. Replaces {0}, {1}, etc. with args.
7973    pub fn tf(&self, key: &str, args: &[&str]) -> String {
7974        let mut result = self.t(key);
7975        for (i, arg) in args.iter().enumerate() {
7976            result = result.replace(&format!("{{{}}}", i), arg);
7977        }
7978        result
7979    }
7980}
7981
7982/// Global localization manager.
7983pub struct L10n {
7984    bundles: HashMap<String, L10nBundle>,
7985    current: String,
7986}
7987
7988impl L10n {
7989    pub fn new(default_locale: &str) -> Self {
7990        Self {
7991            bundles: HashMap::new(),
7992            current: default_locale.to_string(),
7993        }
7994    }
7995
7996    pub fn add_bundle(&mut self, bundle: L10nBundle) {
7997        self.bundles.insert(bundle.locale.clone(), bundle);
7998    }
7999
8000    pub fn set_locale(&mut self, locale: &str) {
8001        self.current = locale.to_string();
8002    }
8003    pub fn current_locale(&self) -> &str {
8004        &self.current
8005    }
8006
8007    pub fn is_rtl(&self) -> bool {
8008        self.bundles
8009            .get(self.current.as_str())
8010            .map(|b| b.is_rtl)
8011            .unwrap_or(false)
8012    }
8013
8014    pub fn t(&self, key: &str) -> String {
8015        self.bundles
8016            .get(self.current.as_str())
8017            .map(|b| b.t(key))
8018            .unwrap_or_else(|| key.to_string())
8019    }
8020
8021    pub fn tf(&self, key: &str, args: &[&str]) -> String {
8022        let mut result = self.t(key);
8023        for (i, arg) in args.iter().enumerate() {
8024            result = result.replace(&format!("{{{}}}", i), arg);
8025        }
8026        result
8027    }
8028
8029    pub fn direction(&self) -> Direction {
8030        if self.is_rtl() {
8031            Direction::RTL
8032        } else {
8033            Direction::LTR
8034        }
8035    }
8036}
8037
8038static L10N: once_cell::sync::Lazy<Arc<RwLock<L10n>>> =
8039    once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(L10n::new("en"))));
8040
8041pub fn init_l10n(l10n: L10n) {
8042    if let Ok(mut guard) = L10N.write() {
8043        *guard = l10n;
8044    }
8045}
8046
8047pub fn l10n() -> Arc<RwLock<L10n>> {
8048    L10N.clone()
8049}
8050
8051pub fn t(key: &str) -> String {
8052    L10N.read()
8053        .map(|g| g.t(key).to_string())
8054        .unwrap_or_else(|_| key.to_string())
8055}
8056
8057pub fn tf(key: &str, args: &[&str]) -> String {
8058    L10N.read()
8059        .map(|g| g.tf(key, args))
8060        .unwrap_or_else(|_| key.to_string())
8061}
8062
8063pub fn set_locale(locale: &str) {
8064    if let Ok(mut guard) = L10N.write() {
8065        guard.set_locale(locale);
8066    }
8067}
8068
8069pub fn current_locale() -> String {
8070    L10N.read()
8071        .map(|g| g.current_locale().to_string())
8072        .unwrap_or_else(|_| "en".to_string())
8073}
8074
8075pub fn is_rtl() -> bool {
8076    L10N.read().map(|g| g.is_rtl()).unwrap_or(false)
8077}
8078
8079// =============================================================================
8080// SYSTEM THEME DETECTION -- Dark/Light mode detection
8081// =============================================================================
8082//
8083// OS-agnostic theme detection. Checks the CVKG_THEME environment variable first,
8084// then falls back to dark mode (safe default).
8085//
8086// Platform backends may override this with native OS queries (e.g.,
8087// dark-light crate on desktop, prefers-color-scheme on web).
8088
8089/// The detected system theme.
8090#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
8091pub enum SystemTheme {
8092    /// Dark mode (default).
8093    #[default]
8094    Dark,
8095    /// Light mode.
8096    Light,
8097}
8098
8099/// Detect the current system theme.
8100///
8101/// Checks `CVKG_THEME` environment variable first:
8102/// - `"dark"` → `SystemTheme::Dark`
8103/// - `"light"` → `SystemTheme::Light`
8104/// - unset or any other value → `SystemTheme::Dark` (default)
8105///
8106/// Platform backends can call this and override with native detection
8107/// (e.g., `dark-light` crate on desktop, `prefers-color-scheme` on web).
8108pub fn detect_system_theme() -> SystemTheme {
8109    std::env::var("CVKG_THEME")
8110        .ok()
8111        .and_then(|v| match v.as_str() {
8112            "light" => Some(SystemTheme::Light),
8113            "dark" => Some(SystemTheme::Dark),
8114            _ => None,
8115        })
8116        .unwrap_or(SystemTheme::Dark)
8117}
8118
8119// =============================================================================
8120// AUDIO / HAPTIC -- Item 14: Spatial Audio / Haptic Feedback
8121// =============================================================================
8122// OS-agnostic: pure trait abstractions. Platform backends via cfg in renderer.
8123
8124pub mod audio_haptic;
8125pub use audio_haptic::{
8126    AudioEngine, HapticEngine, HapticIntensity, NullAudioEngine, NullHapticEngine, haptic_error,
8127    haptic_impact, haptic_selection, haptic_success, play_sound, set_audio_engine,
8128    set_haptic_engine, sounds,
8129};
8130
8131// =============================================================================
8132// PARALLAX -- Depth-based scroll offset system
8133// =============================================================================
8134
8135pub mod parallax;
8136pub use parallax::{DisplayEnvironment, ParallaxModifier, PerformanceContract, Tier3Fallback};
8137
8138// =============================================================================
8139// KVASIR IDENTITY -- Platform-wide unique identifier (crosscrate.md Finding #2)
8140// =============================================================================
8141
8142/// Platform-wide unique identifier used by every CVKG graph layer.
8143///
8144/// # Why this exists
8145/// The crosscrate audit (Finding #2) identified that each crate maintained its own
8146/// incompatible `NodeId(u64)` newtype, causing type-level friction whenever two
8147/// layers needed to reference the same object (e.g., VDOM ↔ Scene sync).
8148///
8149/// # Contract
8150/// - Every `KvasirId` produced by [`KvasirId::new`] is globally unique within
8151///   a single process lifetime (backed by a monotonic atomic counter).
8152/// - IDs are sequential and cache-friendly in `HashMap` / `BTreeMap` keys.
8153/// - `KvasirId(0)` is **reserved as the null/invalid sentinel** — never returned
8154///   by `new()`.
8155/// - `Serialize`/`Deserialize` round-trips through the inner `u64`.
8156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
8157pub struct KvasirId(pub u64);
8158
8159impl KvasirId {
8160    /// The null sentinel value. Never allocated by [`KvasirId::new`].
8161    pub const NULL: KvasirId = KvasirId(0);
8162
8163    /// Allocate a new process-unique `KvasirId`.
8164    ///
8165    /// Uses a relaxed atomic increment — order does not matter because IDs
8166    /// only need to be distinct, not sequentially ordered relative to other
8167    /// memory operations.
8168    pub fn new() -> Self {
8169        use std::sync::atomic::{AtomicU64, Ordering};
8170        static COUNTER: AtomicU64 = AtomicU64::new(1);
8171        KvasirId(COUNTER.fetch_add(1, Ordering::Relaxed))
8172    }
8173
8174    /// Returns `true` if this is the null sentinel value.
8175    pub fn is_null(self) -> bool {
8176        self.0 == 0
8177    }
8178}
8179
8180impl std::fmt::Display for KvasirId {
8181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8182        write!(f, "KvasirId({})", self.0)
8183    }
8184}
8185
8186/// Lossless conversion from a raw `u64` into a `KvasirId`.
8187///
8188/// # Why this exists
8189/// The crosscrate audit (Phase 1 of the implementation plan) unifies identity
8190/// across `cvkg-scene`, `cvkg-vdom`, and `cvkg-flow` by making each crate's
8191/// `NodeId` a type alias for `KvasirId`. Existing call sites that constructed
8192/// `NodeId(some_u64)` need a way to migrate without touching every literal.
8193///
8194/// # Contract
8195/// - `u64` -> `KvasirId` is infallible (any `u64` is a valid id; 0 maps to NULL).
8196/// - `KvasirId` -> `u64` is infallible (trivially the inner value).
8197///
8198/// # Note
8199/// Allocating ids should still go through `KvasirId::new()` so that the
8200/// atomic counter is respected. `From<u64>` is for *existing* ids that came
8201/// from serialized data or stable test fixtures.
8202impl From<u64> for KvasirId {
8203    fn from(value: u64) -> Self {
8204        KvasirId(value)
8205    }
8206}
8207
8208impl From<KvasirId> for u64 {
8209    fn from(id: KvasirId) -> Self {
8210        id.0
8211    }
8212}
8213
8214// =============================================================================
8215// INVALIDATION MODEL -- Platform-wide dirty flag system (crosscrate.md Finding #3)
8216// =============================================================================
8217
8218/// Bitmask encoding which pipeline layers are dirty for a given object.
8219///
8220/// # Why this exists
8221/// The crosscrate audit (Finding #3) identified that each crate had its own
8222/// `is_dirty: bool` field with no shared semantic. Without a unified model,
8223/// updates propagate as full-tree redraws instead of targeted passes, leading
8224/// to performance collapse at scale.
8225///
8226/// # Layers (in pipeline order)
8227/// - `STATE`     — application-level data changed (triggers LAYOUT + PAINT + COMPOSITE)
8228/// - `LAYOUT`    — size or position changed (triggers PAINT + COMPOSITE)
8229/// - `PAINT`     — visual appearance changed (triggers COMPOSITE only)
8230/// - `COMPOSITE` — compositing properties changed (e.g. opacity, transform, blur)
8231///
8232/// # Contract
8233/// A crate that dirtifies a layer MUST also dirtify all downstream layers.
8234/// Use the helper constants [`DirtyFlags::from_state_change`] etc. rather
8235/// than setting bits manually to ensure the invariant is maintained.
8236#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
8237pub struct DirtyFlags(pub u8);
8238
8239impl DirtyFlags {
8240    /// No layers are dirty.
8241    pub const CLEAN: DirtyFlags = DirtyFlags(0b0000_0000);
8242    /// Application state changed — propagates to all downstream layers.
8243    pub const STATE: DirtyFlags = DirtyFlags(0b0000_1111);
8244    /// Layout (size/position) changed — propagates to paint + composite.
8245    pub const LAYOUT: DirtyFlags = DirtyFlags(0b0000_0111);
8246    /// Paint (visual) changed — propagates to composite.
8247    pub const PAINT: DirtyFlags = DirtyFlags(0b0000_0011);
8248    /// Compositing properties changed (opacity, clip, backdrop).
8249    pub const COMPOSITE: DirtyFlags = DirtyFlags(0b0000_0001);
8250    /// All layers dirty (equivalent to STATE).
8251    pub const ALL: DirtyFlags = DirtyFlags(0b0000_1111);
8252
8253    /// Returns `true` if any dirty bits are set.
8254    #[inline]
8255    pub fn is_dirty(self) -> bool {
8256        self.0 != 0
8257    }
8258
8259    /// Returns `true` if the composite layer needs reprocessing.
8260    #[inline]
8261    pub fn needs_composite(self) -> bool {
8262        self.0 & 0b0000_0001 != 0
8263    }
8264
8265    /// Returns `true` if the paint layer needs reprocessing.
8266    #[inline]
8267    pub fn needs_paint(self) -> bool {
8268        self.0 & 0b0000_0010 != 0
8269    }
8270
8271    /// Returns `true` if layout needs reprocessing.
8272    #[inline]
8273    pub fn needs_layout(self) -> bool {
8274        self.0 & 0b0000_0100 != 0
8275    }
8276
8277    /// Returns `true` if application state has changed.
8278    #[inline]
8279    pub fn needs_state(self) -> bool {
8280        self.0 & 0b0000_1000 != 0
8281    }
8282
8283    /// Merge another set of flags into this one (bitwise OR).
8284    #[inline]
8285    pub fn merge(self, other: DirtyFlags) -> DirtyFlags {
8286        DirtyFlags(self.0 | other.0)
8287    }
8288
8289    /// Clear all dirty flags, marking this object as clean.
8290    #[inline]
8291    pub fn clear(self) -> DirtyFlags {
8292        DirtyFlags::CLEAN
8293    }
8294}
8295
8296impl std::ops::BitOr for DirtyFlags {
8297    type Output = DirtyFlags;
8298    fn bitor(self, rhs: DirtyFlags) -> DirtyFlags {
8299        DirtyFlags(self.0 | rhs.0)
8300    }
8301}
8302
8303impl std::ops::BitOrAssign for DirtyFlags {
8304    fn bitor_assign(&mut self, rhs: DirtyFlags) {
8305        self.0 |= rhs.0;
8306    }
8307}
8308
8309impl std::ops::BitAnd for DirtyFlags {
8310    type Output = DirtyFlags;
8311    fn bitand(self, rhs: DirtyFlags) -> DirtyFlags {
8312        DirtyFlags(self.0 & rhs.0)
8313    }
8314}
8315
8316/// A single invalidation record associating a `KvasirId` with its dirty layers.
8317///
8318/// # Contract
8319/// Invalidation records are produced by any system that mutates state and
8320/// consumed by the scheduler to determine what work must be done next frame.
8321#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8322pub struct InvalidationRecord {
8323    /// The object that was mutated.
8324    pub id: KvasirId,
8325    /// Which pipeline layers need reprocessing.
8326    pub flags: DirtyFlags,
8327}
8328
8329impl InvalidationRecord {
8330    /// Create a new invalidation record.
8331    pub fn new(id: KvasirId, flags: DirtyFlags) -> Self {
8332        Self { id, flags }
8333    }
8334
8335    /// Create a record indicating the object's full pipeline needs rebuilding.
8336    pub fn full(id: KvasirId) -> Self {
8337        Self { id, flags: DirtyFlags::ALL }
8338    }
8339}
8340
8341#[cfg(test)]
8342mod kvasir_identity_tests {
8343    use super::*;
8344
8345    #[test]
8346    fn kvasir_id_new_is_non_zero() {
8347        // Contract: KvasirId::new() must never return the null sentinel.
8348        let id = KvasirId::new();
8349        assert!(!id.is_null(), "KvasirId::new() returned null sentinel");
8350    }
8351
8352    #[test]
8353    fn kvasir_id_new_is_unique() {
8354        // Each call must produce a distinct ID.
8355        let a = KvasirId::new();
8356        let b = KvasirId::new();
8357        let c = KvasirId::new();
8358        assert_ne!(a, b);
8359        assert_ne!(b, c);
8360        assert_ne!(a, c);
8361    }
8362
8363    #[test]
8364    fn kvasir_id_null_sentinel() {
8365        assert!(KvasirId::NULL.is_null());
8366        assert!(!KvasirId::new().is_null());
8367    }
8368
8369    #[test]
8370    fn kvasir_id_serde_roundtrip() {
8371        let id = KvasirId(42);
8372        let json = serde_json::to_string(&id).unwrap();
8373        let decoded: KvasirId = serde_json::from_str(&json).unwrap();
8374        assert_eq!(id, decoded);
8375    }
8376
8377    #[test]
8378    fn dirty_flags_clean_is_not_dirty() {
8379        assert!(!DirtyFlags::CLEAN.is_dirty());
8380    }
8381
8382    #[test]
8383    fn dirty_flags_all_implies_all_layers() {
8384        let f = DirtyFlags::ALL;
8385        assert!(f.needs_state());
8386        assert!(f.needs_layout());
8387        assert!(f.needs_paint());
8388        assert!(f.needs_composite());
8389    }
8390
8391    #[test]
8392    fn dirty_flags_composite_only() {
8393        let f = DirtyFlags::COMPOSITE;
8394        assert!(!f.needs_state());
8395        assert!(!f.needs_layout());
8396        assert!(!f.needs_paint());
8397        assert!(f.needs_composite());
8398    }
8399
8400    #[test]
8401    fn dirty_flags_merge() {
8402        let a = DirtyFlags::COMPOSITE;
8403        let b = DirtyFlags::PAINT;
8404        let merged = a.merge(b);
8405        assert!(merged.needs_composite());
8406        assert!(merged.needs_paint());
8407        assert!(!merged.needs_layout());
8408    }
8409
8410    #[test]
8411    fn dirty_flags_bitor() {
8412        let combined = DirtyFlags::PAINT | DirtyFlags::COMPOSITE;
8413        assert!(combined.needs_paint());
8414        assert!(combined.needs_composite());
8415    }
8416
8417    #[test]
8418    fn dirty_flags_clear() {
8419        let dirty = DirtyFlags::ALL;
8420        let clean = dirty.clear();
8421        assert!(!clean.is_dirty());
8422    }
8423
8424    #[test]
8425    fn dirty_flags_serde_roundtrip() {
8426        let f = DirtyFlags::LAYOUT;
8427        let json = serde_json::to_string(&f).unwrap();
8428        let decoded: DirtyFlags = serde_json::from_str(&json).unwrap();
8429        assert_eq!(f, decoded);
8430    }
8431
8432    #[test]
8433    fn invalidation_record_full() {
8434        let id = KvasirId::new();
8435        let rec = InvalidationRecord::full(id);
8436        assert_eq!(rec.id, id);
8437        assert!(rec.flags.needs_state());
8438        assert!(rec.flags.needs_layout());
8439    }
8440}
8441
8442// =========================================================================
8443// P1-15: Subscriber List Mutex Poisoning
8444// =========================================================================
8445//
8446// Regression tests for the audit finding: a single panicking subscriber
8447// would poison the Mutex and break all future state updates forever.
8448// The fix wraps each callback in catch_unwind, so panics are isolated
8449// and logged without affecting other subscribers or future updates.
8450
8451#[cfg(test)]
8452mod subscriber_panic_isolation_tests {
8453    use super::*;
8454    use std::sync::atomic::{AtomicUsize, Ordering};
8455
8456    #[test]
8457    fn panicking_subscriber_does_not_poison_mutex() {
8458        let state = State::new(0i32);
8459        let fired = Arc::new(AtomicUsize::new(0));
8460
8461        // First subscriber: panics.
8462        let _ = state.subscribe(|_| -> () {
8463            panic!("subscriber 1 explodes");
8464        });
8465
8466        // Second subscriber: should still fire.
8467        let fired_clone = Arc::clone(&fired);
8468        let _ = state.subscribe(move |v| {
8469            fired_clone.store(*v as usize + 1, Ordering::SeqCst);
8470        });
8471
8472        // Trigger the state change. Subscriber 1 panics; subscriber 2 runs.
8473        state.set(42);
8474
8475        assert_eq!(
8476            fired.load(Ordering::SeqCst),
8477            43,
8478            "second subscriber must fire even though first panicked"
8479        );
8480
8481        // Critical: future state updates must still work.
8482        let fired2 = Arc::new(AtomicUsize::new(0));
8483        let fired2_clone = Arc::clone(&fired2);
8484        let _ = state.subscribe(move |v| {
8485            fired2_clone.store(*v as usize, Ordering::SeqCst);
8486        });
8487        state.set(100);
8488        assert_eq!(
8489            fired2.load(Ordering::SeqCst),
8490            100,
8491            "future updates must work after subscriber panic"
8492        );
8493    }
8494
8495    #[test]
8496    fn all_subscribers_fire_even_if_one_panics() {
8497        let state = State::new(0u32);
8498        let count = Arc::new(AtomicUsize::new(0));
8499
8500        // Mix of panicking and counting subscribers.
8501        let _ = state.subscribe(|_| panic!("boom 1"));
8502        let c1 = Arc::clone(&count);
8503        let _ = state.subscribe(move |_| {
8504            c1.fetch_add(1, Ordering::SeqCst);
8505        });
8506        let _ = state.subscribe(|_| panic!("boom 2"));
8507        let c2 = Arc::clone(&count);
8508        let _ = state.subscribe(move |_| {
8509            c2.fetch_add(1, Ordering::SeqCst);
8510        });
8511
8512        state.set(1);
8513
8514        // Both non-panicking subscribers must have fired.
8515        assert_eq!(
8516            count.load(Ordering::SeqCst),
8517            2,
8518            "both non-panicking subscribers should fire"
8519        );
8520    }
8521
8522    #[test]
8523    fn invoke_subscribers_safely_returns_count() {
8524        // Direct unit test of the helper function.
8525        use std::sync::Mutex;
8526        let subs: SubscriberList<u32> = Arc::new(Mutex::new(Vec::new()));
8527
8528        let count1 = Arc::new(AtomicUsize::new(0));
8529        let count1_clone = Arc::clone(&count1);
8530        subs.lock().unwrap().push(Box::new(move |v| {
8531            count1_clone.store(*v as usize, Ordering::SeqCst);
8532        }));
8533
8534        let count2 = Arc::new(AtomicUsize::new(0));
8535        let count2_clone = Arc::clone(&count2);
8536        subs.lock().unwrap().push(Box::new(move |v| {
8537            count2_clone.store(*v as usize + 100, Ordering::SeqCst);
8538        }));
8539
8540        let invoked = invoke_subscribers_safely(&subs, &7);
8541        assert_eq!(invoked, 2, "both subscribers should be invoked");
8542        assert_eq!(count1.load(Ordering::SeqCst), 7);
8543        assert_eq!(count2.load(Ordering::SeqCst), 107);
8544    }
8545}
8546
8547// =========================================================================
8548// P1-17: Suspense::new_async Shared Fallback Runtime
8549// =========================================================================
8550//
8551// Regression tests for the audit finding: when no ambient tokio
8552// runtime exists, new_async spawned a new OS thread + runtime per
8553// call. The fix introduces a process-wide shared fallback runtime.
8554
8555#[cfg(test)]
8556mod p1_17_shared_fallback_runtime_tests {
8557    use super::*;
8558    use std::time::Duration;
8559
8560    #[test]
8561    fn fallback_runtime_is_shared() {
8562        // Calling fallback_runtime() multiple times should return the
8563        // same Runtime instance (singleton via OnceLock). This is the
8564        // core invariant that bounds thread creation.
8565        let r1 = fallback_runtime();
8566        let r2 = fallback_runtime();
8567        assert!(
8568            std::ptr::eq(r1 as *const _, r2 as *const _),
8569            "fallback_runtime must return the same instance"
8570        );
8571    }
8572
8573    #[test]
8574    fn fallback_worker_count_is_bounded() {
8575        // The worker count must be >= 1 and <= 8 regardless of host
8576        // CPU count. This is what prevents the audit's "spawns
8577        // hundreds of OS threads" issue.
8578        let n = *FALLBACK_WORKER_COUNT.get_or_init(|| {
8579            let available = std::thread::available_parallelism()
8580                .map(|n| n.get())
8581                .unwrap_or(2);
8582            available.saturating_sub(1).clamp(1, 8)
8583        });
8584        assert!(n >= 1, "worker count must be at least 1, got {n}");
8585        assert!(n <= 8, "worker count must be at most 8, got {n}");
8586    }
8587
8588    #[test]
8589    fn many_suspense_calls_share_runtime() {
8590        // P1-17 regression: 20 new_async calls in quick succession
8591        // should not hang or OOM. They all share the single
8592        // fallback runtime, so we never create more than ~8 OS
8593        // threads regardless of call count.
8594        //
8595        // We use a counter SharedState to confirm all 20 futures
8596        // actually run to completion.
8597        let counter = State::new(0u32);
8598        let mut handles = Vec::new();
8599        for _ in 0..20 {
8600            let s = Suspense::new_async(async { Ok::<u32, String>(1) });
8601            // Each suspense ready()s after the future resolves.
8602            // We don't block on ready (would deadlock without
8603            // explicit tokio context), but the spawn is enough to
8604            // exercise the path.
8605            let _ = s; // suppress unused warning
8606            handles.push(s);
8607        }
8608        // Force the counter to tick so the test observably runs.
8609        counter.set(20);
8610        assert_eq!(counter.get(), 20);
8611        // If we got here, the test did not hang or panic, which is
8612        // the main thing we want to verify for P1-17.
8613    }
8614
8615    // ==========================================
8616    // P1-14: State<T> redundant storage documentation
8617    // ==========================================
8618
8619    #[test]
8620    fn p1_14_state_storage_mechanisms() {
8621        // P1-14 documentation test: State<T> has 4 storage
8622        // mechanisms (swap, metadata_swap, tvar, metadata_tvar).
8623        // The audit flagged this as redundant. The fix is to
8624        // document the trade-off (arc_swap for reads, TVar for
8625        // atomic compound transactions) and add a set_direct()
8626        // method for callers who don't need compound transactions.
8627        use std::mem::size_of;
8628        let state = State::new(42u32);
8629        // State contains 4 storage mechanisms + subscribers +
8630        // version + resolution.
8631        // This test documents the size and the trade-off.
8632        let size = size_of_val(&state);
8633        // Size should be at least the size of 4 Arcs (4*8=32 on
8634        // 64-bit) plus subscribers (1 Arc) plus version (1 Arc)
8635        // plus ConflictResolution (1 byte tag).
8636        assert!(
8637            size >= 4 * std::mem::size_of::<usize>(),
8638            "State<T> should be at least 4 Arcs in size"
8639        );
8640    }
8641
8642    #[test]
8643    fn p1_14_set_direct_updates_value() {
8644        // P1-14: set_direct() bypasses TVar for simple updates.
8645        // The swap is the authoritative read source.
8646        let state = State::new(0u32);
8647        state.set_direct(42);
8648        assert_eq!(state.get(), 42);
8649    }
8650
8651    #[test]
8652    fn p1_14_set_direct_notifies_subscribers() {
8653        // P1-14: set_direct() must notify subscribers just like
8654        // set().
8655        let state = State::new(0u32);
8656        let received = Arc::new(Mutex::new(Vec::new()));
8657        let received_clone = Arc::clone(&received);
8658        state.subscribe(move |v| {
8659            received_clone.lock().unwrap().push(*v);
8660        });
8661        state.set_direct(1);
8662        state.set_direct(2);
8663        state.set_direct(3);
8664        // Allow the subscriber invocations to complete.
8665        std::thread::sleep(std::time::Duration::from_millis(10));
8666        let log = received.lock().unwrap();
8667        // Should have at least the last 3 values, but the order
8668        // and count depend on how many subscribers were invoked
8669        // (subscribers can be invoked synchronously or batched).
8670        assert!(
8671            log.contains(&1) && log.contains(&2) && log.contains(&3),
8672            "set_direct must notify subscribers of all values"
8673        );
8674    }
8675}
8676
8677// =========================================================================
8678// P1-39: DirtyRegionManager -- tracks changed rectangles
8679// =========================================================================
8680//
8681// The P1-39 audit found that the scene graph lacks dirty region
8682// tracking. Large UIs may redraw excessively when only a small
8683// region changes. This struct provides the foundation for
8684// future dirty-region optimizations.
8685//
8686// Currently it just stores a list of dirty rectangles. Future
8687// work would add:
8688//  - Coalescing adjacent dirty regions into larger rects
8689//  - Tree-based hierarchical dirty tracking
8690//  - Integration with the renderer's scissor/clip
8691//
8692// This is a passive container -- callers add dirty regions
8693// when they change something, and the renderer can clear
8694// them after a frame.
8695
8696/// P1-39: a list of regions that have changed and need to be
8697/// re-rendered. Coalesces overlapping rectangles on add to
8698/// avoid unbounded growth.
8699#[derive(Debug, Clone, Default)]
8700pub struct DirtyRegionManager {
8701    /// The dirty rectangles, in screen-space coordinates.
8702    regions: Vec<Rect>,
8703    /// Counter incremented on each clear, useful for detecting
8704    /// "stale" dirty regions after multiple frames.
8705    generation: u64,
8706}
8707
8708impl DirtyRegionManager {
8709    /// Create a new empty dirty region manager.
8710    pub fn new() -> Self {
8711        Self::default()
8712    }
8713
8714    /// Mark a region as dirty. The region is in screen-space
8715    /// coordinates (typically the same coordinate system as the
8716    /// rest of the rendering).
8717    ///
8718    /// If `region` overlaps with an existing dirty region, the
8719    /// two are coalesced into a single larger rectangle. This
8720    /// prevents the dirty list from growing unbounded for
8721    /// large UIs with many small changes.
8722    pub fn mark_dirty(&mut self, region: Rect) {
8723        // Try to merge with an existing overlapping region.
8724        for existing in self.regions.iter_mut() {
8725            if Self::rects_overlap(*existing, region) {
8726                *existing = Self::union_rect(*existing, region);
8727                return;
8728            }
8729        }
8730        // No overlap -- add as new region.
8731        self.regions.push(region);
8732    }
8733
8734    /// Get the current dirty regions. The renderer can use
8735    /// this list to clip drawing to only the changed areas.
8736    pub fn regions(&self) -> &[Rect] {
8737        &self.regions
8738    }
8739
8740    /// Check if any region is dirty. Useful for skipping a
8741    /// frame when nothing has changed.
8742    pub fn is_dirty(&self) -> bool {
8743        !self.regions.is_empty()
8744    }
8745
8746    /// Clear all dirty regions. Called by the renderer after
8747    /// processing a frame.
8748    ///
8749    /// Increments the generation counter so callers can detect
8750    /// when a clear has happened.
8751    pub fn clear(&mut self) {
8752        self.regions.clear();
8753        self.generation = self.generation.wrapping_add(1);
8754    }
8755
8756    /// Get the current generation counter. Increases on every
8757    /// clear(). Callers can cache this to detect when the
8758    /// dirty state has been reset.
8759    pub fn generation(&self) -> u64 {
8760        self.generation
8761    }
8762
8763    /// Number of dirty regions currently tracked. After
8764    /// coalescing, this should be much smaller than the number
8765    /// of mark_dirty() calls.
8766    pub fn len(&self) -> usize {
8767        self.regions.len()
8768    }
8769
8770    /// Check whether the dirty region list is empty.
8771    pub fn is_empty(&self) -> bool {
8772        self.regions.is_empty()
8773    }
8774
8775    /// Check if two rectangles overlap.
8776    fn rects_overlap(a: Rect, b: Rect) -> bool {
8777        a.x < b.x + b.width
8778            && a.x + a.width > b.x
8779            && a.y < b.y + b.height
8780            && a.y + a.height > b.y
8781    }
8782
8783    /// Compute the union of two rectangles (the smallest
8784    /// rectangle that contains both).
8785    fn union_rect(a: Rect, b: Rect) -> Rect {
8786        let min_x = a.x.min(b.x);
8787        let min_y = a.y.min(b.y);
8788        let max_x = (a.x + a.width).max(b.x + b.width);
8789        let max_y = (a.y + a.height).max(b.y + b.height);
8790        Rect {
8791            x: min_x,
8792            y: min_y,
8793            width: max_x - min_x,
8794            height: max_y - min_y,
8795        }
8796    }
8797}
8798
8799#[cfg(test)]
8800mod p1_39_dirty_region_tests {
8801    use super::{DirtyRegionManager, Rect};
8802
8803    #[test]
8804    fn new_manager_is_empty() {
8805        let m = DirtyRegionManager::new();
8806        assert!(!m.is_dirty());
8807        assert!(m.is_empty());
8808        assert_eq!(m.len(), 0);
8809    }
8810
8811    #[test]
8812    fn mark_dirty_adds_region() {
8813        let mut m = DirtyRegionManager::new();
8814        m.mark_dirty(Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 });
8815        assert!(m.is_dirty());
8816        assert_eq!(m.len(), 1);
8817    }
8818
8819    #[test]
8820    fn overlapping_regions_coalesce() {
8821        let mut m = DirtyRegionManager::new();
8822        m.mark_dirty(Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 });
8823        m.mark_dirty(Rect { x: 5.0, y: 5.0, width: 10.0, height: 10.0 });
8824        // Should be coalesced into a single region.
8825        assert_eq!(m.len(), 1);
8826        let r = &m.regions()[0];
8827        assert_eq!(r.x, 0.0);
8828        assert_eq!(r.y, 0.0);
8829        assert_eq!(r.width, 15.0);
8830        assert_eq!(r.height, 15.0);
8831    }
8832
8833    #[test]
8834    fn non_overlapping_regions_dont_coalesce() {
8835        let mut m = DirtyRegionManager::new();
8836        m.mark_dirty(Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 });
8837        m.mark_dirty(Rect { x: 100.0, y: 100.0, width: 10.0, height: 10.0 });
8838        // Should remain as 2 separate regions.
8839        assert_eq!(m.len(), 2);
8840    }
8841
8842    #[test]
8843    fn clear_resets_regions_and_increments_generation() {
8844        let mut m = DirtyRegionManager::new();
8845        m.mark_dirty(Rect { x: 0.0, y: 0.0, width: 10.0, height: 10.0 });
8846        let g1 = m.generation();
8847        m.clear();
8848        assert!(!m.is_dirty());
8849        assert_eq!(m.len(), 0);
8850        assert_eq!(m.generation(), g1 + 1);
8851    }
8852
8853    #[test]
8854    fn many_overlapping_marks_coalesce_to_one() {
8855        let mut m = DirtyRegionManager::new();
8856        // Mark 100 overlapping small regions.
8857        for i in 0..100 {
8858            m.mark_dirty(Rect {
8859                x: i as f32,
8860                y: i as f32,
8861                width: 10.0,
8862                height: 10.0,
8863            });
8864        }
8865        // All should coalesce to a single region.
8866        assert_eq!(m.len(), 1);
8867    }
8868}
8869
8870// =========================================================================
8871// P1-43: FrameBudget -- global frame budget contract
8872// =========================================================================
8873//
8874// The P1-43 audit found that no global frame budget contract
8875// exists. Individual subsystems may exceed their time allocation
8876// without coordination. P0-2 already handles per-frame
8877// degradation (skipping non-essential passes when over budget)
8878// but doesn't coordinate allocation across subsystems.
8879//
8880// This struct provides the foundation for future frame budget
8881// coordination. It tracks wall-clock time per frame and per
8882// subsystem, and allows callers to check whether a subsystem
8883// is within its allocated time slice.
8884//
8885// Currently a passive observer. Future work would add:
8886//  - Per-subsystem time allocation
8887//  - Automatic QualityLevel adjustment when over budget
8888//  - Integration with the renderer's frame loop
8889
8890// =============================================================================
8891// P1-41: LIST / TREE VIRTUALIZATION
8892// =============================================================================
8893
8894/// Outcome of a `VirtualListWindow::compute` call — describes which rows
8895/// should be rendered and what scroll offset to apply to position them.
8896///
8897/// P1-41: IDE and visualization workloads with tens-of-thousands of rows must
8898/// only render the rows visible in the current viewport.  `VirtualListWindow`
8899/// computes the correct row range without building the full row list.
8900#[derive(Debug, Clone, PartialEq)]
8901pub struct VirtualWindow {
8902    /// Index of the first row that should be rendered (inclusive).
8903    pub first_visible: usize,
8904    /// Index one past the last row that should be rendered (exclusive).
8905    pub last_visible: usize,
8906    /// Total estimated height of all rows above `first_visible`, in logical
8907    /// pixels.  Use this as the scroll-offset padding above the rendered rows.
8908    pub offset_before: f32,
8909    /// Total estimated height of all rows below `last_visible`, in logical
8910    /// pixels.  Use this as the placeholder height below the rendered rows.
8911    pub offset_after: f32,
8912}
8913
8914/// Computes the visible slice of a uniform-height virtual list.
8915///
8916/// Contract:
8917/// - `total_rows`: total number of items in the list (can be enormous).
8918/// - `row_height`: uniform logical height of every row in pixels.
8919/// - `viewport_y`: scroll offset of the viewport top edge.
8920/// - `viewport_height`: height of the visible window in pixels.
8921/// - `overscan`: number of extra rows to render above/below the viewport for
8922///   smooth scrolling.  Typically 2–5.
8923///
8924/// Returns a `VirtualWindow` describing the rendered slice and offset padding.
8925/// If `row_height` is ≤ 0 or `total_rows` is 0, returns a zero window.
8926pub fn compute_virtual_list_window(
8927    total_rows: usize,
8928    row_height: f32,
8929    viewport_y: f32,
8930    viewport_height: f32,
8931    overscan: usize,
8932) -> VirtualWindow {
8933    if total_rows == 0 || row_height <= 0.0 {
8934        return VirtualWindow {
8935            first_visible: 0,
8936            last_visible: 0,
8937            offset_before: 0.0,
8938            offset_after: 0.0,
8939        };
8940    }
8941
8942    // How many rows fit in the viewport (rounded up for partial rows).
8943    let visible_rows = (viewport_height / row_height).ceil() as usize;
8944
8945    // First row whose bottom edge is below the viewport top.
8946    let first = (viewport_y / row_height).floor() as isize - overscan as isize;
8947    let first = first.max(0) as usize;
8948
8949    // Last row whose top edge is above the viewport bottom.
8950    let last = first + visible_rows + 2 * overscan;
8951    let last = last.min(total_rows);
8952
8953    VirtualWindow {
8954        first_visible: first,
8955        last_visible: last,
8956        offset_before: first as f32 * row_height,
8957        offset_after: (total_rows - last) as f32 * row_height,
8958    }
8959}
8960
8961/// Computes the visible slice of a variable-height virtual list using
8962/// a precomputed prefix-sum of row heights.
8963///
8964/// Contract:
8965/// - `prefix_heights[i]` is the cumulative height of all rows 0..i (not
8966///   including row i).  `prefix_heights.len()` must equal `total_rows + 1`
8967///   where `prefix_heights[0] == 0` and `prefix_heights[total_rows]` is the
8968///   total list height.
8969/// - `viewport_y` and `viewport_height` are in the same logical pixel units.
8970/// - `overscan` works the same as in `compute_virtual_list_window`.
8971///
8972/// This is O(log N) via binary search on the prefix-sum array.
8973pub fn compute_virtual_list_window_variable(
8974    prefix_heights: &[f32],
8975    viewport_y: f32,
8976    viewport_height: f32,
8977    overscan: usize,
8978) -> VirtualWindow {
8979    let total_rows = prefix_heights.len().saturating_sub(1);
8980    if total_rows == 0 {
8981        return VirtualWindow {
8982            first_visible: 0,
8983            last_visible: 0,
8984            offset_before: 0.0,
8985            offset_after: 0.0,
8986        };
8987    }
8988
8989    // Binary search for the first row whose cumulative top is >= viewport_y.
8990    let first_idx = prefix_heights
8991        .partition_point(|&h| h < viewport_y)
8992        .saturating_sub(1);
8993    let first = first_idx.saturating_sub(overscan);
8994
8995    // Binary search for the last row whose top < viewport_y + viewport_height.
8996    let viewport_bottom = viewport_y + viewport_height;
8997    let last_idx = prefix_heights.partition_point(|&h| h < viewport_bottom);
8998    let last = (last_idx + overscan).min(total_rows);
8999
9000    VirtualWindow {
9001        first_visible: first,
9002        last_visible: last,
9003        offset_before: prefix_heights[first],
9004        offset_after: prefix_heights[total_rows] - prefix_heights[last],
9005    }
9006}