Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apps/kit-host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ edition = "2021"
description = "Generic app shell rendering a Splash component kit as native makepad widgets (fork-free, upstream makepad)"

[dependencies]
makepad-widgets = { git = "https://github.com/makepad/makepad.git", branch = "dev" }
# The L0 profile, so this host can realize and lower a card at runtime. Depends
# on serde_json alone — it carries no makepad lineage, which is what makes it
# safe here and is why it was extracted from splash-core.
splash-ui-l0 = { path = "../../../Splash/crates/splash-ui-l0" }
serde_json = "1"

makepad-widgets = { path = "../../../makepad-splash/widgets" }
splash-widgets = { path = "../../crates/splash-widgets" }
splash-render = { path = "../../crates/splash-render" }
splash-makepad = { path = "../../crates/splash-makepad" }
Expand Down
69 changes: 69 additions & 0 deletions apps/kit-host/src/data/news.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"lead": [
{
"id": "1",
"title": "Rust 1.95 lands with const generics improvements",
"author": "steveklabnik",
"points": 412,
"comments": 137,
"url": "blog.rust-lang.org"
}
],
"feed": [
{
"id": "2",
"title": "A new approach to incremental compilation",
"author": "nikomatsakis",
"points": 288
},
{
"id": "3",
"title": "Show HN: I built a terminal UI framework",
"author": "jdoe",
"points": 194
},
{
"id": "4",
"title": "The case against microservices, revisited",
"author": "mfowler",
"points": 151
},
{
"id": "5",
"title": "WebGPU shipping in all major browsers",
"author": "kvark",
"points": 133
},
{
"id": "6",
"title": "Why your database is slower than you think",
"author": "aphyr",
"points": 97
},
{
"id": "7",
"title": "Notes on distributed consensus",
"author": "hjfreyd",
"points": 74
},
{
"id": "8",
"title": "An illustrated guide to lifetimes",
"author": "fasterthanlime",
"points": 61
}
],
"article": {
"title": "Rust 1.95 lands",
"author": "steveklabnik",
"points": 412,
"comments": 137,
"url": "blog.rust-lang.org"
},
"selected": "",
"env": {
"locale": {
"lang": "en"
}
}
}
60 changes: 60 additions & 0 deletions apps/kit-host/src/data/stock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"movers": [
{
"ticker": "NVDA",
"name": "NVIDIA",
"last": 184.2,
"change": 3.1,
"pct": 1.7
},
{
"ticker": "AAPL",
"name": "Apple",
"last": 231.4,
"change": -1.2,
"pct": -0.5
},
{
"ticker": "MSFT",
"name": "Microsoft",
"last": 429.8,
"change": 2.4,
"pct": 0.6
},
{
"ticker": "TSLA",
"name": "Tesla",
"last": 248.9,
"change": -4.7,
"pct": -1.9
}
],
"quote": {
"name": "NVIDIA",
"last": 184.2,
"change": 3.1,
"pct": 1.7,
"open": 181.0,
"high": 185.6,
"low": 180.2,
"volume": 41200000,
"mktcap": 4520000000000,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Secret Detected (credit_card_visa)

Visa Credit Card Number detected: LLM validation unavailable - flagging conservatively

Match preview: 4520****0000

Remediation: Please review manually and remove if this is a real secret

"pe": 58.3
},
"series": {
"points": [
1,
2,
3
],
"min": 180.2,
"max": 185.6
},
"selected": "",
"range": "m1",
"env": {
"locale": {
"lang": "en"
}
}
}
93 changes: 93 additions & 0 deletions apps/kit-host/src/data/weather.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"place": {
"name": "Kyoto"
},
"now": {
"temp": 18,
"feels": 16,
"hi": 21,
"lo": 12,
"cond": 2,
"humidity": 61,
"wind": 9,
"pressure": 1013,
"uv": 3,
"visibility": 10
},
"sun": {
"rise": 5.1,
"set": 18.9,
"now": 13.5
},
"moon": {
"phase": 0.62,
"illumination": 0.71
},
"aqi": {
"grid": [
1,
2,
3
],
"index": 42,
"band": "good"
},
"scene": "https://image.pollinations.ai/prompt/kyoto%20skyline?width=1080&height=1920&nologo=true",
"units": "c",
"days": 7,
"city": "Kyoto",
"env": {
"locale": {
"temp_unit": "c",
"lang": "en"
}
},
"week": {
"days": [
{
"dayname": "Mon",
"hi": 21,
"lo": 12,
"cond": 2
},
{
"dayname": "Tue",
"hi": 23,
"lo": 13,
"cond": 0
},
{
"dayname": "Wed",
"hi": 19,
"lo": 11,
"cond": 3
},
{
"dayname": "Thu",
"hi": 17,
"lo": 10,
"cond": 3
},
{
"dayname": "Fri",
"hi": 20,
"lo": 11,
"cond": 1
},
{
"dayname": "Sat",
"hi": 24,
"lo": 14,
"cond": 0
},
{
"dayname": "Sun",
"hi": 22,
"lo": 13,
"cond": 1
}
],
"min_lo": 10,
"max_hi": 24
}
}
141 changes: 141 additions & 0 deletions apps/kit-host/src/l0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! L0 cards, as routes this host can render.
//!
//! This is what "the kit is wired on this backend" means, and it was the missing
//! piece: `components/l0/_kit.splash` existed, `kit::lower` emitted calls to it,
//! `splash_render` evaluated them and `to_makepad_ui` mapped them — and nothing
//! anywhere mounted the result. The whole path was tested and unrenderable.
//!
//! Every reference card becomes a route:
//!
//! ```text
//! l0/news -> realize -> kit::lower -> _kit.splash -> splash_render
//! ```
//!
//! **The data is baked and static.** These are the same blobs the profile's own
//! tests use, so what appears here is what those tests assert — and where a
//! source would be answered live on a backend that can fetch, this one shows the
//! seeded value. That is a real difference from octos-one and it is why the two
//! are not expected to look identical.

use splash_ui_l0::{kit, realize, RealizeLimits};

/// The theme. Every L0 route is this plus one lowered card.
const KIT: &str = include_str!("../../../components/l0/_kit.splash");

const NEWS: &str = include_str!("../../../../Splash/crates/splash-ui-l0/tests/fixtures/news.card");
const STOCK: &str = include_str!("../../../../Splash/crates/splash-ui-l0/tests/fixtures/stock.card");
const WEATHER: &str =
include_str!("../../../../Splash/crates/splash-ui-l0/tests/fixtures/weather.card");

const NEWS_DATA: &str = include_str!("data/news.json");
const STOCK_DATA: &str = include_str!("data/stock.json");
const WEATHER_DATA: &str = include_str!("data/weather.json");

/// The routes this module answers, and what each renders.
pub const ROUTES: &[(&str, &str)] = &[
("l0/news", "L0 — news"),
("l0/stock", "L0 — stock"),
("l0/weather", "L0 — weather"),
];

pub fn has(route: &str) -> bool {
ROUTES.iter().any(|(r, _)| *r == route)
}

pub fn title_of(route: &str) -> Option<&'static str> {
ROUTES.iter().find(|(r, _)| *r == route).map(|(_, t)| *t)
}

/// A card's source, realized and lowered against the theme.
///
/// A card that fails to realize returns its diagnostics AS A CARD rather than an
/// empty string: an unrenderable route is otherwise a blank screen, which reads
/// as a layout bug rather than as a rejected card.
pub fn source_for(route: &str) -> String {
let (card, data) = match route {
"l0/news" => (NEWS, NEWS_DATA),
"l0/stock" => (STOCK, STOCK_DATA),
_ => (WEATHER, WEATHER_DATA),
};
let data: serde_json::Value = match serde_json::from_str(data) {
Ok(v) => v,
Err(e) => return failed(&format!("data did not parse: {e}")),
};
let report = realize(card, &data, RealizeLimits::default());
let Some(root) = report.root else {
let why: Vec<String> = report.diagnostics.iter().map(|d| d.message.clone()).collect();
return failed(&why.join("; "));
};
format!("{KIT}\n{}", kit::lower(&root))
}

/// A visible failure, in the kit's own vocabulary.
fn failed(why: &str) -> String {
let escaped = why.replace('\\', "\\\\").replace('"', "\\\"");
format!(
"{KIT}\nlet node = l0_surface([l0_title(\"card did not realize\"), l0_body(\"{escaped}\")])\nnode\n"
)
}

#[cfg(test)]
mod tests {
/// Every L0 route evaluates to a real tree through the whole path.
///
/// This host is the only place in this repository that MOUNTS an L0 card.
/// Before it, the kit, the lowering, the evaluator and the widget mapping
/// were each tested and the composition of them was unrenderable — there
/// was nowhere to put one on screen.
#[test]
fn every_l0_route_builds_a_tree() {
for (route, title) in super::ROUTES {
let src = super::source_for(route);
let tree = splash_render::build(&src, |_vm| {})
.unwrap_or_else(|| panic!("{route} ({title}) evaluated to nil"));
assert!(
tree.count() > 10,
"{route} produced a {}-node tree — too small to be a card",
tree.count()
);
}
}

/// A route that cannot realize renders its reason, not a blank screen.
#[test]
fn a_card_that_does_not_realize_says_why() {
let src = super::failed("the reason");
let tree = splash_render::build(&src, |_vm| {}).expect("the failure card evaluates");
let mut text = String::new();
fn words(n: &splash_render::UiNode, out: &mut String) {
if let Some(t) = n.attrs.text.as_deref() {
out.push_str(t);
}
for c in &n.children {
words(c, out);
}
}
words(&tree, &mut text);
assert!(text.contains("the reason"), "got {text:?}");
}

/// The five data visualisations reach the tree on THIS backend too.
///
/// octos-one proves them against its own native widgets; this proves the
/// same card reaches the same kinds here, which is the claim §1.1 makes
/// about `UiNode` being the branch point.
#[test]
fn the_visualisations_reach_this_backends_tree() {
let tree = splash_render::build(&super::source_for("l0/weather"), |_vm| {})
.expect("weather evaluates");
let mut kinds = Vec::new();
fn walk(n: &splash_render::UiNode, out: &mut Vec<String>) {
out.push(format!("{:?}", n.kind));
for c in &n.children {
walk(c, out);
}
}
walk(&tree, &mut kinds);
for expected in ["TempBar", "SunArc", "MoonPhase", "AqiContour"] {
assert!(kinds.iter().any(|k| k == expected), "{expected} missing");
}
}
}
Loading