1pub mod audio;
58pub mod compute;
59pub mod deterministic;
60pub mod distributed;
61pub mod event;
62pub mod pipeline;
63pub mod tui;
64pub mod web_sys_gen;
65pub mod widget;
66pub mod worker;
67
68pub use audio::{AudioBrick, AudioParam, RingBufferConfig};
70pub use compute::{
71 ComputeBrick, ElementwiseOp, ReduceKind, TensorBinding, TensorType, TileOp, TileStrategy,
72};
73pub use deterministic::{
74 BrickHistory, BrickState, DeterministicBrick, DeterministicClock, DeterministicRng,
75 ExecutionTrace, GuardSeverity, GuardViolation, GuardedBrick, InvariantGuard, StateValue,
76};
77pub use distributed::{
78 Backend, BackendSelector, BrickCoordinator, BrickDataTracker, BrickInput, BrickMessage,
79 BrickOutput, DataLocation, DistributedBrick, ExecutionMetrics, MultiBrickExecutor,
80 SchedulerStats, Subscription, TaskSpec, WorkStealingScheduler, WorkStealingTask, WorkerId,
81 WorkerQueue, WorkerStats,
82};
83pub use event::{EventBinding, EventBrick, EventHandler, EventType};
84pub use pipeline::{
85 AuditEntry, BrickPipeline, BrickStage, Checkpoint, PipelineAuditCollector, PipelineContext,
86 PipelineData, PipelineError, PipelineMetadata, PipelineResult, PrivacyTier, StageTrace,
87 ValidationLevel, ValidationMessage, ValidationResult,
88};
89pub use tui::{
90 AnalyzerBrick, CielabColor, CollectorBrick, CollectorError, PanelBrick, PanelId, PanelState,
91 RingBuffer,
92};
93pub use web_sys_gen::{
94 get_base_url, BlobUrl, CustomEventDispatcher, EventDetail, FetchClient, GeneratedWebSys,
95 GenerationMetadata, PerformanceTiming, WebSysError, GENERATION_METADATA,
96};
97pub use widget::{
98 commands_to_gpu_instances, Canvas, Constraints, CornerRadius, DrawCommand, Event, GpuInstance,
99 LayoutResult, LineCap, LineJoin, Modifiers, RecordingCanvas, Rect, RenderMetrics, Size,
100 StrokeStyle, TextStyle, Transform2D, Widget, WidgetColor, WidgetExt, WidgetMouseButton,
101 WidgetPoint,
102};
103pub use worker::{
104 BrickWorkerMessage, BrickWorkerMessageDirection, FieldType, MessageField, WorkerBrick,
105 WorkerTransition,
106};
107
108use std::time::Duration;
109
110#[derive(Debug, Clone, PartialEq)]
115pub enum BrickAssertion {
116 TextVisible,
118
119 ContrastRatio(f32),
121
122 MaxLatencyMs(u32),
124
125 ElementPresent(String),
127
128 Focusable,
130
131 Custom {
133 name: String,
135 validator_id: u64,
137 },
138}
139
140impl BrickAssertion {
141 #[must_use]
143 pub const fn text_visible() -> Self {
144 Self::TextVisible
145 }
146
147 #[must_use]
149 pub const fn contrast_ratio(ratio: f32) -> Self {
150 Self::ContrastRatio(ratio)
151 }
152
153 #[must_use]
155 pub const fn max_latency_ms(ms: u32) -> Self {
156 Self::MaxLatencyMs(ms)
157 }
158
159 #[must_use]
161 pub fn element_present(selector: impl Into<String>) -> Self {
162 Self::ElementPresent(selector.into())
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct BrickBudget {
172 pub measure_ms: u32,
174 pub layout_ms: u32,
176 pub paint_ms: u32,
178 pub total_ms: u32,
180}
181
182impl BrickBudget {
183 #[must_use]
185 pub const fn uniform(total_ms: u32) -> Self {
186 let phase_ms = total_ms / 3;
187 Self {
188 measure_ms: phase_ms,
189 layout_ms: phase_ms,
190 paint_ms: phase_ms,
191 total_ms,
192 }
193 }
194
195 #[must_use]
197 pub const fn new(measure_ms: u32, layout_ms: u32, paint_ms: u32) -> Self {
198 Self {
199 measure_ms,
200 layout_ms,
201 paint_ms,
202 total_ms: measure_ms + layout_ms + paint_ms,
203 }
204 }
205
206 #[must_use]
208 pub const fn as_duration(&self) -> Duration {
209 Duration::from_millis(self.total_ms as u64)
210 }
211}
212
213impl Default for BrickBudget {
214 fn default() -> Self {
215 Self::uniform(16)
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct BrickVerification {
223 pub passed: Vec<BrickAssertion>,
225 pub failed: Vec<(BrickAssertion, String)>,
227 pub verification_time: Duration,
229}
230
231impl BrickVerification {
232 #[must_use]
234 pub fn is_valid(&self) -> bool {
235 self.failed.is_empty()
236 }
237
238 #[must_use]
240 pub fn score(&self) -> f32 {
241 let total = self.passed.len() + self.failed.len();
242 if total == 0 {
243 1.0
244 } else {
245 self.passed.len() as f32 / total as f32
246 }
247 }
248}
249
250#[derive(Debug, Clone)]
252pub struct BudgetViolation {
253 pub brick_name: String,
255 pub budget: BrickBudget,
257 pub actual: Duration,
259 pub phase: Option<BrickPhase>,
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265pub enum BrickPhase {
266 Measure,
268 Layout,
270 Paint,
272}
273
274pub trait Brick: Send + Sync {
290 fn brick_name(&self) -> &'static str;
292
293 fn assertions(&self) -> &[BrickAssertion];
295
296 fn budget(&self) -> BrickBudget;
298
299 fn verify(&self) -> BrickVerification;
303
304 fn to_html(&self) -> String;
309
310 fn to_css(&self) -> String;
315
316 fn test_id(&self) -> Option<&str> {
318 None
319 }
320
321 fn can_render(&self) -> bool {
323 self.verify().is_valid()
324 }
325}
326
327#[derive(Debug, Clone)]
332pub enum BrickError {
333 AssertionFailed {
335 assertion: BrickAssertion,
337 reason: String,
339 },
340
341 BudgetExceeded(BudgetViolation),
343
344 InvalidTransition {
346 from: String,
348 to: String,
350 reason: String,
352 },
353
354 MissingChild {
356 expected: String,
358 },
359
360 HtmlGenerationFailed {
362 reason: String,
364 },
365}
366
367impl std::fmt::Display for BrickError {
368 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369 match self {
370 Self::AssertionFailed { assertion, reason } => {
371 write!(f, "Assertion {assertion:?} failed: {reason}")
372 }
373 Self::BudgetExceeded(violation) => {
374 write!(
375 f,
376 "Budget exceeded for {}: {:?} > {:?}",
377 violation.brick_name, violation.actual, violation.budget.total_ms
378 )
379 }
380 Self::InvalidTransition { from, to, reason } => {
381 write!(f, "Invalid transition {from} -> {to}: {reason}")
382 }
383 Self::MissingChild { expected } => {
384 write!(f, "Missing required child brick: {expected}")
385 }
386 Self::HtmlGenerationFailed { reason } => {
387 write!(f, "HTML generation failed: {reason}")
388 }
389 }
390 }
391}
392
393impl std::error::Error for BrickError {}
394
395pub type BrickResult<T> = Result<T, BrickError>;
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 struct TestBrick {
403 text: String,
404 visible: bool,
405 }
406
407 impl Brick for TestBrick {
408 fn brick_name(&self) -> &'static str {
409 "TestBrick"
410 }
411
412 fn assertions(&self) -> &[BrickAssertion] {
413 &[
414 BrickAssertion::TextVisible,
415 BrickAssertion::ContrastRatio(4.5),
416 ]
417 }
418
419 fn budget(&self) -> BrickBudget {
420 BrickBudget::uniform(16)
421 }
422
423 fn verify(&self) -> BrickVerification {
424 let mut passed = Vec::new();
425 let mut failed = Vec::new();
426
427 for assertion in self.assertions() {
428 match assertion {
429 BrickAssertion::TextVisible => {
430 if self.visible && !self.text.is_empty() {
431 passed.push(assertion.clone());
432 } else {
433 failed.push((assertion.clone(), "Text not visible".into()));
434 }
435 }
436 BrickAssertion::ContrastRatio(_) => {
437 passed.push(assertion.clone());
439 }
440 _ => passed.push(assertion.clone()),
441 }
442 }
443
444 BrickVerification {
445 passed,
446 failed,
447 verification_time: Duration::from_micros(100),
448 }
449 }
450
451 fn to_html(&self) -> String {
452 format!(r#"<div class="test-brick">{}</div>"#, self.text)
453 }
454
455 fn to_css(&self) -> String {
456 ".test-brick { color: #fff; background: #000; }".into()
457 }
458 }
459
460 #[test]
461 fn test_brick_verification_passes() {
462 let brick = TestBrick {
463 text: "Hello".into(),
464 visible: true,
465 };
466
467 let result = brick.verify();
468 assert!(result.is_valid());
469 assert_eq!(result.score(), 1.0);
470 }
471
472 #[test]
473 fn test_brick_verification_fails() {
474 let brick = TestBrick {
475 text: String::new(),
476 visible: false,
477 };
478
479 let result = brick.verify();
480 assert!(!result.is_valid());
481 assert!(result.score() < 1.0);
482 }
483
484 #[test]
485 fn test_budget_uniform() {
486 let budget = BrickBudget::uniform(30);
487 assert_eq!(budget.total_ms, 30);
488 assert_eq!(budget.measure_ms, 10);
489 }
490
491 #[test]
492 fn test_can_render_valid() {
493 let brick = TestBrick {
494 text: "Hello".into(),
495 visible: true,
496 };
497 assert!(brick.can_render());
498 }
499
500 #[test]
501 fn test_can_render_invalid() {
502 let brick = TestBrick {
503 text: String::new(),
504 visible: false,
505 };
506 assert!(!brick.can_render());
507 }
508
509 #[test]
510 fn test_brick_assertion_constructors() {
511 let text_vis = BrickAssertion::text_visible();
512 assert!(matches!(text_vis, BrickAssertion::TextVisible));
513
514 let contrast = BrickAssertion::contrast_ratio(4.5);
515 assert!(
516 matches!(contrast, BrickAssertion::ContrastRatio(r) if (r - 4.5).abs() < f32::EPSILON)
517 );
518
519 let latency = BrickAssertion::max_latency_ms(100);
520 assert!(matches!(latency, BrickAssertion::MaxLatencyMs(100)));
521
522 let elem = BrickAssertion::element_present("div.test");
523 assert!(matches!(elem, BrickAssertion::ElementPresent(s) if s == "div.test"));
524 }
525
526 #[test]
527 fn test_brick_assertion_focusable() {
528 let focusable = BrickAssertion::Focusable;
529 assert!(matches!(focusable, BrickAssertion::Focusable));
530 }
531
532 #[test]
533 fn test_brick_assertion_custom() {
534 let custom = BrickAssertion::Custom {
535 name: "test_assertion".into(),
536 validator_id: 42,
537 };
538 match custom {
539 BrickAssertion::Custom { name, validator_id } => {
540 assert_eq!(name, "test_assertion");
541 assert_eq!(validator_id, 42);
542 }
543 _ => panic!("Expected Custom variant"),
544 }
545 }
546
547 #[test]
548 fn test_budget_new() {
549 let budget = BrickBudget::new(5, 10, 15);
550 assert_eq!(budget.measure_ms, 5);
551 assert_eq!(budget.layout_ms, 10);
552 assert_eq!(budget.paint_ms, 15);
553 assert_eq!(budget.total_ms, 30);
554 }
555
556 #[test]
557 fn test_budget_default() {
558 let budget = BrickBudget::default();
559 assert_eq!(budget.total_ms, 16); }
561
562 #[test]
563 fn test_budget_as_duration() {
564 let budget = BrickBudget::uniform(100);
565 let duration = budget.as_duration();
566 assert_eq!(duration, Duration::from_millis(100));
567 }
568
569 #[test]
570 fn test_verification_score_empty() {
571 let verification = BrickVerification {
572 passed: vec![],
573 failed: vec![],
574 verification_time: Duration::from_micros(10),
575 };
576 assert_eq!(verification.score(), 1.0); assert!(verification.is_valid());
578 }
579
580 #[test]
581 fn test_verification_score_partial() {
582 let verification = BrickVerification {
583 passed: vec![BrickAssertion::TextVisible],
584 failed: vec![(BrickAssertion::Focusable, "Not focusable".into())],
585 verification_time: Duration::from_micros(10),
586 };
587 assert_eq!(verification.score(), 0.5);
588 assert!(!verification.is_valid());
589 }
590
591 #[test]
592 fn test_brick_phase_variants() {
593 let measure = BrickPhase::Measure;
594 let layout = BrickPhase::Layout;
595 let paint = BrickPhase::Paint;
596
597 assert!(matches!(measure, BrickPhase::Measure));
598 assert!(matches!(layout, BrickPhase::Layout));
599 assert!(matches!(paint, BrickPhase::Paint));
600 assert_ne!(measure, layout);
601 }
602
603 #[test]
604 fn test_budget_violation() {
605 let violation = BudgetViolation {
606 brick_name: "TestBrick".into(),
607 budget: BrickBudget::uniform(16),
608 actual: Duration::from_millis(50),
609 phase: Some(BrickPhase::Paint),
610 };
611 assert_eq!(violation.brick_name, "TestBrick");
612 assert_eq!(violation.phase, Some(BrickPhase::Paint));
613 }
614
615 #[test]
616 fn test_brick_to_html_css() {
617 let brick = TestBrick {
618 text: "Test".into(),
619 visible: true,
620 };
621 let html = brick.to_html();
622 let css = brick.to_css();
623
624 assert!(html.contains("test-brick"));
625 assert!(html.contains("Test"));
626 assert!(css.contains(".test-brick"));
627 }
628
629 #[test]
630 fn test_brick_name() {
631 let brick = TestBrick {
632 text: "Test".into(),
633 visible: true,
634 };
635 assert_eq!(brick.brick_name(), "TestBrick");
636 }
637
638 #[test]
639 fn test_brick_assertions_list() {
640 let brick = TestBrick {
641 text: "Test".into(),
642 visible: true,
643 };
644 let assertions = brick.assertions();
645 assert_eq!(assertions.len(), 2);
646 assert!(matches!(assertions[0], BrickAssertion::TextVisible));
647 }
648
649 #[test]
650 fn test_brick_budget_method() {
651 let brick = TestBrick {
652 text: "Test".into(),
653 visible: true,
654 };
655 let budget = brick.budget();
656 assert_eq!(budget.total_ms, 16);
657 }
658}