//! Aspirational RUIN app API for async resources and effects. //! //! Intentionally non-compiling; this is a design sketch. use ruin::prelude::*; #[ruin_runtime::async_main] async fn main() -> ruin::Result<()> { App::new() .window(Window::new().title("RUIN Issue Dashboard").size(1240.0, 820.0)) .mount::() .run() .await } #[component] fn IssueDashboard() -> impl View { let repo = use_signal(|| "wtemple/ruin".to_string()); let selected_issue = use_signal(|| None::); let issues = use_resource(move || { let repo = repo.get(); async move { github::list_issues(&repo).await } }); let details = use_resource(move || { let repo = repo.get(); let issue_id = selected_issue.get(); async move { let issue_id = issue_id?; github::issue_details(&repo, issue_id).await.ok() } }); use_effect(move || { tracing::info!( repo = %repo.get(), selected_issue = ?selected_issue.get(), "dashboard dependencies changed" ); }); view! { row(fill = true, gap = 20, padding = 20) { block(width = 360, gap = 12) { text(role = TextRole::Heading(1), size = 28, weight = FontWeight::Semibold) { "Issues" } text_input(value = repo, placeholder = "owner/repo") {} suspense(fallback = || view! { ProgressSpinner(label = "Loading issues...") {} }) { match issues.read() { Ok(items) => view! { list(gap = 8) { for issue in items keyed by issue.id { button( kind = if selected_issue.get() == Some(issue.id) { ButtonKind::Primary } else { ButtonKind::Secondary }, on_press = move |_| selected_issue.set(Some(issue.id)), ) { issue.title } } } }, Err(error) => view! { ErrorPanel( title = "Failed to load issues", detail = error.to_string(), ) {} }, } } } block(fill = true, gap = 16) { text(role = TextRole::Heading(2), size = 24, weight = FontWeight::Semibold) { "Details" } match details.read() { Pending => view! { ProgressSpinner(label = "Loading issue details...") {} }, Ready(Some(issue)) => view! { column(gap = 12) { text(role = TextRole::Heading(3), size = 22, weight = FontWeight::Semibold) { issue.title } Markdown(source = issue.body) {} } }, Ready(None) => view! { EmptyState(title = "Pick an issue") { "The detail panel should track the current selection and re-run only \ the async work that actually depends on it." } }, } } } } }