#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TemplateContext {
Home,
Project,
}
#[derive(Debug, Clone, Copy)]
pub struct Template {
pub name: &'static str,
pub context: TemplateContext,
pub description: &'static str,
pub body: &'static str,
}
pub const TEMPLATES: &[Template] = &[
Template {
name: "home_splash",
context: TemplateContext::Home,
description: "Terminal name as a giant figlet hero with a postfx reveal, plus ambient weather / moon / year-progress.",
body: include_str!("home_splash.toml"),
},
Template {
name: "home_daily",
context: TemplateContext::Home,
description: "Large time-of-day hero; weather / sunrise / moon on the left and year/month/week/day gauges on the right.",
body: include_str!("home_daily.toml"),
},
Template {
name: "home_github",
context: TemplateContext::Home,
description: "Avatar + handle + contribution heatmap, plus open PRs / review queue and recent activity timeline.",
body: include_str!("home_github.toml"),
},
Template {
name: "home_minimal",
context: TemplateContext::Home,
description: "Quiet three-line preset: a time-of-day greeting, one-line date, and the daily quote — no figlet, lots of whitespace.",
body: include_str!("home_minimal.toml"),
},
Template {
name: "home_feed",
context: TemplateContext::Home,
description: "Morning briefing: time-of-day figlet, Hacker News top stories, on-this-day + sky panel, and today's Wikipedia featured article as the read-more lift.",
body: include_str!("home_feed.toml"),
},
Template {
name: "project_splash",
context: TemplateContext::Project,
description: "Repo name as a giant figlet hero with a postfx reveal, slug / description / license underneath — nothing else. For repo-shipped configs.",
body: include_str!("project_splash.toml"),
},
Template {
name: "project_github",
context: TemplateContext::Project,
description: "Dynamic repo hero + subtitle; branch state, commit / CI sparklines, open PRs, and recent releases.",
body: include_str!("project_github.toml"),
},
Template {
name: "project_minimal",
context: TemplateContext::Project,
description: "Quiet preset: repo name, latest tag, and the last three commits. Fully offline (no GitHub calls) — acknowledges the repo without taking over the screen.",
body: include_str!("project_minimal.toml"),
},
Template {
name: "project_codebase",
context: TemplateContext::Project,
description: "Repo analytics dashboard: project-name figlet at the centre flanked by files-by-dir and largest-files rankings, with LOC / age / comment-density tiles above and churn / TODO list / TODO hotspots below. Local-only — no network or auth.",
body: include_str!("project_codebase.toml"),
},
];
pub fn find(name: &str) -> Option<&'static Template> {
TEMPLATES.iter().find(|t| t.name == name)
}
pub fn for_context(context: TemplateContext) -> impl Iterator<Item = &'static Template> {
TEMPLATES.iter().filter(move |t| t.context == context)
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::config::DashboardConfig;
use crate::config::SettingsConfig;
use crate::fetcher::Registry as FetcherRegistry;
use crate::layout::WidgetId;
use crate::payload::{Body, ImageData, Payload};
use crate::render::{
self, Registry as RenderRegistry, Shape, test_utils::render_to_buffer_with_layout_and_specs,
};
use crate::runtime;
use crate::samples;
fn render_registry() -> RenderRegistry {
RenderRegistry::with_builtins()
}
fn fetcher_registry() -> FetcherRegistry {
FetcherRegistry::with_builtins()
}
fn sample_payload(fetcher: &crate::fetcher::RegisteredFetcher, shape: Shape) -> Payload {
let body = fetcher
.sample_body(shape)
.or_else(|| samples::canonical_sample(shape))
.unwrap_or_else(|| {
Body::Image(ImageData {
path: "/tmp/splashboard-nonexistent.png".into(),
})
});
Payload {
icon: None,
status: None,
format: None,
body,
}
}
fn dashboard_for(template: &Template) -> DashboardConfig {
DashboardConfig::parse(template.body)
.unwrap_or_else(|e| panic!("{} failed to parse: {e}", template.name))
}
#[test]
fn find_returns_matching_template_and_none_for_unknown_name() {
let known = find("home_splash").unwrap();
assert_eq!(known.name, "home_splash");
assert_eq!(known.context, TemplateContext::Home);
assert!(find("missing-template").is_none());
}
#[test]
fn for_context_filters_templates_in_declaration_order() {
let homes = for_context(TemplateContext::Home)
.map(|template| template.name)
.collect::<Vec<_>>();
let projects = for_context(TemplateContext::Project)
.map(|template| template.name)
.collect::<Vec<_>>();
assert_eq!(
homes,
TEMPLATES
.iter()
.filter(|template| template.context == TemplateContext::Home)
.map(|template| template.name)
.collect::<Vec<_>>()
);
assert_eq!(
projects,
TEMPLATES
.iter()
.filter(|template| template.context == TemplateContext::Project)
.map(|template| template.name)
.collect::<Vec<_>>()
);
}
#[test]
fn every_template_parses() {
for t in TEMPLATES {
let _ = dashboard_for(t);
}
}
#[test]
fn every_widget_fetcher_is_registered() {
let reg = fetcher_registry();
for t in TEMPLATES {
let d = dashboard_for(t);
for w in &d.widgets {
assert!(
reg.get(&w.fetcher).is_some(),
"{}: widget `{}` uses unknown fetcher `{}`",
t.name,
w.id,
w.fetcher,
);
}
}
}
#[test]
fn every_widget_renderer_is_registered() {
let reg = render_registry();
for t in TEMPLATES {
let d = dashboard_for(t);
for w in &d.widgets {
if let Some(spec) = &w.render {
let name = spec.renderer_name();
assert!(
reg.get(name).is_some(),
"{}: widget `{}` uses unknown renderer `{}`",
t.name,
w.id,
name,
);
}
}
}
}
#[test]
fn every_widget_id_is_referenced_by_layout() {
for t in TEMPLATES {
let d = dashboard_for(t);
let defined: std::collections::HashSet<_> =
d.widgets.iter().map(|w| w.id.clone()).collect();
for row in &d.rows {
for child in &row.children {
let Some(id) = child.widget.as_deref() else {
continue;
};
assert!(
defined.contains(id),
"{}: layout references unknown widget `{}`",
t.name,
id,
);
}
}
}
}
#[test]
fn every_template_renders_without_panic() {
let fetchers = fetcher_registry();
let renderers = render_registry();
for t in TEMPLATES {
let d = dashboard_for(t);
let config = crate::config::Config::from_parts(SettingsConfig::default(), d);
let shapes = runtime::derive_shapes(&config.widgets, &fetchers, &renderers);
let mut payloads: HashMap<WidgetId, Payload> = HashMap::new();
let mut specs: HashMap<WidgetId, render::RenderSpec> = HashMap::new();
for w in &config.widgets {
let shape = shapes
.get(&w.id)
.copied()
.or_else(|| fetchers.get(&w.fetcher).map(|f| f.default_shape()))
.unwrap_or(Shape::Text);
let fetcher = fetchers
.get(&w.fetcher)
.unwrap_or_else(|| panic!("{}: unknown fetcher `{}`", t.name, w.fetcher));
payloads.insert(w.id.clone(), sample_payload(&fetcher, shape));
if let Some(spec) = w.render.clone() {
specs.insert(w.id.clone(), spec);
}
}
let layout = config.to_layout();
let _ = render_to_buffer_with_layout_and_specs(&layout, &payloads, &specs, 100, 40);
}
}
}