Repository navigation
The five data visualisations, and a host that can mount an L0 card - #7
Conversation
In-flight work on this repository's own catalog: the host shell that renders a component kit as native makepad widgets, the reference catalog's screens baked in (cargo-makepad builds Android inside a generated wrapper crate that never runs a build script, which is why they are included rather than read), the generated index, and the Material lowering in `splash-makepad`. Committed on its own, ahead of the L0 work that builds on it, because it is a different subject and a commit should be about one thing. `material.rs` goes with it rather than staying untracked: `lib.rs` declares `pub mod material`, so without it a fresh clone does not build. A commit that compiles only because of files in someone's working tree is not a commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
§1.1 named this unsettled and said `Shader` with a variant "was dismissed too quickly". It is settled the other way, and the reason is typing: with an anonymous shader a temperature bar and a moon phase ride the same nameless fields, and nothing catches a card passing a latitude where a phase belongs. `min`/`max` and `lat`/`lon` already meant the right thing and are reused; `lo`, `hi`, `rise`, `set`, `now`, `phase`, `illum`, `span`, `symbol` and `range` are new. `padtop`/`padbottom` and `margintop`/`marginbottom` come with them: a page's top padding clears the status bar and its bottom the gesture bar, and said as one symmetric `pady` the card sat 30px too high. THREE SHADERS ARE REAL — a temperature bar as a segment of the week's range, the sun's path as a half-ellipse with now marked on it, a moon disc whose terminator tracks the phase. TWO SAY THEY HAVE NOTHING. An air-quality field and a price series are DATA, and this backend has no fetch. They draw an explicit hatched empty state rather than a plausible curve: a card that looks complete and is fiction is worse than one that says it has nothing, and worse than the marker it replaced. The kit's type scale is TAKEN from the widgets it replaces, not chosen, so a golden failure means "the layer moved" rather than "the theme changed". `l0_tinted` composes a direction around a role instead of folding a parameter into all seven text functions, six of which would pass 0 forever. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pieces were all here and the composition was unrenderable: the kit existed,
`kit::lower` emitted calls to it, `splash_render` evaluated them and
`to_makepad_ui` mapped them to widgets — and nothing in this repository ever put
the result on a screen. Every layer had tests; the path had nowhere to end.
Three routes, ahead of the catalog in the index because they are the reason this
host exists now:
l0/news -> realize -> kit::lower -> _kit.splash -> splash_render
`splash-ui-l0` is a real dependency rather than a dev one — the host realizes and
lowers at RUNTIME, so the card stays a ledger instead of a baked string. Possible
only because the profile was extracted from `splash-core` and needs `serde_json`
alone.
THE DATA IS BAKED AND STATIC here, which is a real difference from octos-one and
not an oversight: where a source is answered live on a backend that fetches, this
one shows the seeded value. Saying so in the code is cheaper than discovering it
from a screenshot.
A card that fails to realize renders its DIAGNOSTICS. A blank route reads as a
layout bug; the reason reads as a rejected card.
I SHIPPED FIVE DANGLING WIDGET NAMES in the commit that added the kinds —
`widget_name` mapped them to `L0TempBar` and friends and none existed. Nothing
failed, because the mapping and the definitions live in different crates and
nothing here renders to pixels. The new test walks all three cards, collects
every name this backend emits, and asserts each one the kit owns is defined;
renaming a definition fails it by name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🤖 AI Code ReviewClaude CLI exited with code 1: ✅ No significant issues found. Tokens used: 0 | Powered by r9s.ai |
| "high": 185.6, | ||
| "low": 180.2, | ||
| "volume": 41200000, | ||
| "mktcap": 4520000000000, |
There was a problem hiding this comment.
🟠 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
| ("badge", include_str!("../../../components/material/screens/badge.splash")), | ||
| ("bottomappbar", include_str!("../../../components/material/screens/bottomappbar.splash")), | ||
| ("bottomnav", include_str!("../../../components/material/screens/bottomnav.splash")), | ||
| ("bottomsheet", include_str!("../../../components/material/screens/bottomsheet.splash")), |
There was a problem hiding this comment.
🟠 Secret Detected (aws_secret_key)
Potential AWS Secret Key detected: LLM validation unavailable - flagging conservatively
Match preview: /com****heet
Remediation: Please review manually and remove if this is a real secret
| ("loadingindicator", include_str!("../../../components/material/screens/loadingindicator.splash")), | ||
| ("materialswitch", include_str!("../../../components/material/screens/materialswitch.splash")), | ||
| ("menu", include_str!("../../../components/material/screens/menu.splash")), | ||
| ("musicplayer", include_str!("../../../components/material/screens/musicplayer.splash")), |
There was a problem hiding this comment.
🟠 Secret Detected (aws_secret_key)
Potential AWS Secret Key detected: LLM validation unavailable - flagging conservatively
Match preview: /com****ayer
Remediation: Please review manually and remove if this is a real secret
| ("navigationdrawer", include_str!("../../../components/material/screens/navigationdrawer.splash")), | ||
| ("navigationrail", include_str!("../../../components/material/screens/navigationrail.splash")), | ||
| ("octoswidgets", include_str!("../../../components/material/screens/octoswidgets.splash")), | ||
| ("preferences", include_str!("../../../components/material/screens/preferences.splash")), |
There was a problem hiding this comment.
🟠 Secret Detected (aws_secret_key)
Potential AWS Secret Key detected: LLM validation unavailable - flagging conservatively
Match preview: /com****nces
Remediation: Please review manually and remove if this is a real secret
| ("octoswidgets", include_str!("../../../components/material/screens/octoswidgets.splash")), | ||
| ("preferences", include_str!("../../../components/material/screens/preferences.splash")), | ||
| ("progressindicator", include_str!("../../../components/material/screens/progressindicator.splash")), | ||
| ("radiobutton", include_str!("../../../components/material/screens/radiobutton.splash")), |
There was a problem hiding this comment.
🟠 Secret Detected (aws_secret_key)
Potential AWS Secret Key detected: LLM validation unavailable - flagging conservatively
Match preview: /com****tton
Remediation: Please review manually and remove if this is a real secret
…raws it with `Map` was admitted by the catalog and lowered by neither backend, so the nav card — the one §1.0 cites as settling its central argument — drew an error box where the map goes. Admitted at L0 was true; the same screen was not. `l0_map(mode, zoom, lat, lon, poly)` closes that, and `polyline` on `Attrs` is what carries the route: the catalog note claimed the widget fetches its own, and it does not — `nav_polyline` is a live field `MapView` renders and never populates. So the fetch is the card's declared source resolved into a call, the same shape every other live value takes. Also `l0_fit` (a row that stops filling), `l0_satellite` (the cloud pane), and `l0_row`/`l0_grid` chunking, each added because a reference card asked for it and got an unsupported-role box instead.
…st's to hold The weather card gained a `Satellite` cloud pane and a `Row` that stops filling, and swapped its visibility tile for precipitation. Six nodes. The number was raised only after BOTH evaluators independently reported 66. They agreed with each other and disagreed with the file, which is the one direction that means the fixture moved rather than a walk drifting — the whole reason the count lives in a file both suites read. `a_card_carries_its_text_through_the_kit` stops checking for a seeded headline. `sys.news` is answered live now, so a story title lowers to the CALL and a bare VM has no `sys` to run it. What the test guards is that words survive the kit at all, and the card's own `copy` proves that without depending on a value the backend fetches. octos-one's twin test was fixed this way already; this is the other half.
`l0_surface_map`, and an opaque `l0_sheet` for the one fill in this palette that cannot be translucent. Every number came off `a2app/apps/nav`, which has four maps and puts none of them in a column. A `MapView` is a fixed-pixel full-bleed surface that paints its route through the GPU nav projection rather than inside a laid-out rect, so beneath the card's content in a column it draws straight over it. Measured on a OnePlus 6: the trip's FROM/TO fields, its duration and its Go button were invisible under the map, and in the driving view the route ribbon cut the turn banner's street name in half. Three values each cost a screenshot: - FIXED 812, not `fillh`. `Fill` inside a `Fit` parent resolves to 0 and a card is an item in a chat list, so filling rendered an entirely empty screen. - The sheet is OPAQUE. `l0_panel` is 7% white — a window over a map, not a surface, and the map's road labels read straight through the trip. - The sheet sits at the BOTTOM, because `update_plan_preview_camera` fits the route into the band ABOVE the summary sheet. A sheet at the top lands exactly on the route it was making room for. 76 at the bottom rather than the shipping card's 30: that card is full-screen and an L0 card sits under the app's composer bar, which cut its last row in half.
…ansparent `l0_panel` was BOTH `let l0_panel = argb(18,255,255,255)` and `fn l0_panel(kids)`. From the function's declaration onward the name resolves to the function, and a function coerced to a colour is 0 — so `l0_chip` and `l0_field` each asked for that fill and each got fully transparent. Uses INSIDE `l0_panel`'s own body still resolved to the colour, which is why panels looked right and chips did not, and why it read as a chip bug rather than a naming one. On screen: a nav card's Go and End, and the stock card's four unselected range chips, rendered as bare text with no button under them. Nothing missing, nothing misplaced — they just did not look tappable. The colour is `l0_fill` now. `l0_panel` is only ever the role. It is the file's one collision; the other ten palette entries share no name with a function. Found by live-testing on a OnePlus 6, and no test here could have seen it: zero is a perfectly valid colour. `a_filled_role_reaches_the_tree_with_its_fill` in octos-one now compares a chip's fill against a panel's, so a rename that misses one of them fails. Also `l0_field` drops to 15pt from 17. A place name is the longest string a card puts in a fixed-width box, and "Saratoga High School" rendered as "Saratoga High Schoc" — clipped inside its own field with no ellipsis to say so. The shipping nav card's search input is 15.
`l0_map` gains a `gpsfollow` flag, set by the lowering only when the `at:` binding
really is `sys.gps`.
A kit value is evaluated when the ledger resolves, so a baked centre only moves
when the whole card re-resolves — realize, lower, evaluate, rebuild the widget tree
— which at a fix a second is the entire card every second. The app this replaces
was built around avoiding exactly that: its drive screen is a no-rebuild
`fn tick()` and its contract says never to force a rebuild while driving.
With the flag the renderer emits `sys.gps("lat")` as a live expression instead of
the number, and the camera follows per frame with no rebuild. Measured on a
OnePlus 6 at a realistic 54 km/h: 10-81% CPU and 1.2 GB before, 0-11% and 1.0 GB
after.
A flag rather than letting the renderer assume `sys.gps` for any follow map — that
would be right today and wrong the first time a card follows something else.
The flag existed so the renderer could emit `sys.gps("lat")` into the map's centre
property. Nothing re-evaluates a property, so that froze the camera completely: 21
fixes, ~105 m of travel, 0.0% of pixels different on a OnePlus 6.
A follow camera now reads the platform's last fix directly, every frame, in the
widget. `follow`/`follow3d` are only emitted when the card declared a live position,
so the mode alone says when to do that and the flag says nothing extra.
`l0_map` is back to the five arguments it had.
The kit evaluates a `sys.*` call to a string, and that string was all the renderer saw — so refreshing it meant re-resolving the ledger and re-parsing the whole document, which rebuilds the `MapView` inside it. Measured on a OnePlus 6: frame hitches and card re-resolves correlate 1:1, up to 327 ms of frozen map. `action` carries the call so the renderer can name the widget and set it from `fn tick()` — which is what the nav app this replaces has always done, and why its contract forbids forcing a rebuild while driving.
`l0_surface_map` stacked the map, a top-aligned layer and a bottom-aligned layer as three siblings, each filling the screen with its own alignment. `a2app/apps/nav` does not: it puts ONE layer over the map in a `Down` flow, with the top pane as the first child, a `fillh` spacer eating the slack, and the sheet last. Theirs is better for a reason worth recording — two filling layers renegotiated their size every frame, and the card re-resolved ~1000 times a minute. The single column took that to 3. `l0_reveal` is content a swipe uncovers, and `l0_field` pads instead of fixing its height so its text is centred by construction: `h: 48` laid the run at the top of the box and every place name floated above its pill. Bottom padding is 76 where theirs is 10 — their card is full-screen and an L0 card sits under the app's composer bar.
`l0_chip_danger` is the shipping nav card's `endrow` — #7a1f1f on #ffd9d9, full width, centred — and the sheet centres its children as that card centres `remmin`. A card says `tone: .danger`; which colour that is, and that it spans the sheet rather than hugging its label, is the theme's.
`padbottom` 76 -> 52. 76 cleared the app's composer bar by ~70 logical units, so the sheet floated in the middle of the map rather than resting at the edge. 52 is the bar's own height plus a hair, which is what "at the bottom" means for a card that lives in a chat list.
Three numbers, measured rather than guessed. `padbottom` 52 left 258px — 11% of the screen — of empty map between the sheet and the edge, so it read as hovering; 14 and 30 put it under the app's chrome, where the distance was clipped. 40 rests it just clear. The sheet's own padding drops from 8/12 to 4/8. It sits over a map a driver is reading, so every unit of its height is map they cannot see — expanded it covered 22% of the screen, and collapsed it is now a handle and a number.
`nav_markers` in the widget was reachable only through `ui.<id>.set_route_markers(…)`, a method call — and a declarative backend sets properties. So the L0 nav card drew a correct route with no origin or destination pin, and `draw_nav_route_pins` returned on its first line every frame. Nothing looked broken; the route was just less legible than the card it replaced, which is the failure mode this profile keeps producing. `Attrs::markers` is a VALUE for the same reason `polyline` is one: whoever built the node already resolved the endpoints, so deriving the pins there means they cannot mark a place the drawn line does not pass. The L2 card composed the string by hand from its own `let`s and pushed it separately, which is two chances to disagree. Found by mapping the L2 app's §3 requirements, which this repo's parity document had never covered.
`l0_map` takes what the card NAMED — `.none`, `.zoom`, `.all` — and draws the pill and the recenter button top-right, clear of the status bar and of the sheet, where the card being replaced puts them and where a right thumb reaches without covering the route. The buttons carry an ACTION and no call; the backend supplies the call. The recenter ring is DRAWN as two circles rather than typed. "◎" is U+25CE and the bundled Roboto has no glyph for it, so on device it rendered a tofu box — the L2 card gets away with the character only because it never names a font, and every L0 text run does. A ring is two circles and needs no font at all.
Ten declared sizes, one factor. Title 26→18, body 17→12, rows and chips 14→10, captions 11→8, values 20→14, the field 15→11, the destructive chip 15→11. The hierarchy is what matters and it is unchanged: a hero is still the largest thing on a card and a caption still the smallest, in the same proportion. Only the scale moved, so nothing has to be re-decided about which role outranks which.
`Attrs.changeto`, beside `tapto`. They are different moments carrying different events: a keystroke asks "what am I looking for" and a return says "this is where I am going". `TextInput` has called both back all along — `on_change` with the text so far, `on_return` with the committed text — so this is plumbing a capability the widget already had up to where a card can name it.
FROM, TO and VIA fitted each to its own text, so each started its field at a different x — the same ragging the rank and day columns above were given fixed widths to fix. Now the labels end where the fields begin. A WRAPPER, not an alignment on the label. Alignment positions a widget inside its parent; it does not move the run inside the widget. Setting a width and an align on the text itself left the run starting at the box's left edge and clipping off its right — "TO" rendered as "TC", and widening the box moved everything without un-clipping anything, which is what said the width was not the problem. The backend already learned this about `TextInput`, where the container's `align` did nothing and `label_align` was the property that moved the text.
…er one Two things a screen was being given whether it asked or not. `l0_band` draws nothing when nothing is docked at the top. Every planning screen carried an empty dark pill over its map — a container with no children is not a container. The flag comes from the LOWERING because the kit cannot ask a list its length, and that is the right split anyway: the card is what knows whether it docked something. `l0_chip_primary` is the action a screen is FOR — twice the type on a round target, which is what an icon needs to be tappable at arm's length. A separate role rather than a size parameter, for the same reason `.danger` is one: it is bigger type AND a rounder target AND a lit fill, and threading three presentation decisions through a flag reads worse than naming the thing.
… means `tone: .primary` already said "the thing this screen is for". This decides what that looks like: the green iOS Maps spends on GO, on a rounded fill with dark text. The card does not name the colour and could not — `.primary` is what it says, and a palette entry is what answers. Two new ones rather than reusing `l0_up`: a rise and an action are different meanings that happen to share a hue today, and collapsing them would make one of them wrong the first time the other changes.
`route_badge` on a map node, and the widget draws it as a bubble on the path at the
route's half-way point — half way ALONG it, not half way between its ends, or a route
that loops away and back labels open country nowhere near the line.
`route_badge` and not `badge`: `Attrs` already had one for a component's count. Two
meanings under one name is how a field ends up carrying whichever the last writer
meant, and a blind rename of mine caught the original in two eval files — the
compiler only noticed because the struct literal then had the field twice.
The separator is a PIPE. `\u{1}` was the obvious choice and does not survive: emitted
through `{:?}` it becomes six literal characters in the DSL, the widget's split finds
no control character, and both facts render as one run. A duration and a distance
cannot contain a pipe.
The primary chip was 20pt with 22/14 of padding — roughly 250x110 on a 1080-wide
screen, a quarter of the sheet for one word. 15pt with 24/9 is in proportion with
iOS Maps' own GO. And the sheet's 4/8 padding was tuned for the DRIVE sheet, which is
a handle over two lines of text; a sheet ending in a button needs 10/18, or the button
sits 7px off the edge and reads as clipped whether or not it is.
l0_surface_map takes the control column (and has_side) and lays it out in its own flow below the banner — inside l0_map the pill sat at a fixed offset and the instruction banner covered its "+", and two clusters fought for one corner. l0_mapcontrols nests the card's side-docked kids rather than concatenating lists (the VM has no `+` for lists; the concatenation evaluated to an error and every control vanished), and l0_mapside gives them the pill's dark backing so a 2D/3D chip reads over pale tiles. Zoom pill, then the location ring, then the card's own chips — the order the eye-test asked for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
l0_mapchip draws a side-docked map control at the recenter ring's 38x38; the side column loses its own box (a circle in a rounded panel is two backgrounds). The surface's padbottom goes 40 → 56, measured rather than tasted: with 40 the sheet's bottom sat within ~14dp of the clip boundary, and where that boundary falls moves with the window's inset history — the same build rendered the bottom padding after a keyboard cycle and clipped GO's rounding flat on a fresh boot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The weather fixture gained §5.12's saved-cities layer (Splash d2cc719 / 089f87f): a strip panel and a tappable "add a city" row that evaluate even in the conformance data's empty state — nothing saved, editor closed. Six nodes is the honest cost of that state. Raised by the file's own rule: both evaluators (this repo's l0_cards and octos-one's l0_eval walk) independently reported 72 against the grown fixture — they agreed with each other and disagreed with this file, the one direction that means the fixture moved rather than a walk drifting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
15 -> 16: one wrapper row per test mover so the star chip escapes the tappable row's hit target. Both evaluators reported 16 independently before the number moved, per this file's own rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
27 -> 41: a header, four suggestion chips, and the followed-topic rows (store-backed, so empty in the fixture — the delta is the static block). Both evaluators reported 41 independently before the number moved, per this file's own rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
72 -> 71: the add-a-city row under the strip became a + chip in the title bar, net one node fewer. Both evaluators reported 71 independently before the number moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
l0_chip_danger_row: danger's colours in a chip's geometry, for the Remove that sits beside the row it deletes; the spanning variant stays the screen action. Stock 16 -> 21: the add flow's always-present header bar joins the list view; both evaluators reported 21 independently before the number moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both evaluators reported 17 independently before the number moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each of the three test movers grows its wrapper and hidden chip. Both evaluators reported 22 independently before the number moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both evaluators reported 17 independently before the number moved. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replaces #6, which folded in-flight work into commits about L0. Three commits,
each about one thing.
Pairs with OctoSense-org/OctoScript#4, which holds the profile side.
be0f9d8· the catalog host, its screens, and the Material rendererIn-flight work on this repository's own catalog, committed on its own ahead of
the L0 work that builds on it.
material.rsgoes with it rather than stayinguntracked —
lib.rsdeclarespub mod material, so without it a fresh clonedoes not build, and a commit that compiles only because of files in someone's
working tree is not a commit.
22434ac· five kinds, and their shaders§1.1 said
Shaderwith avariant"was dismissed too quickly". It is settledthe other way, and the reason is typing: with an anonymous shader a
temperature bar and a moon phase ride the same nameless fields, and nothing
catches a card passing a latitude where a phase belongs.
Three shaders are real — a temperature bar as a segment of the week's range,
the sun's path as a half-ellipse with now marked on it, a moon disc whose
terminator tracks the phase.
Two say they have nothing. An air-quality field and a price series are
data, and this backend has no fetch. They draw an explicit hatched empty state
rather than a plausible curve: a card that looks complete and is fiction is worse
than one that says it has nothing, and worse than the marker it replaced.
The kit's type scale is taken from the widgets it replaces, not chosen, so a
golden failure means "the layer moved" and not "the theme changed".
6506198· somewhere to put a cardThe pieces were all here and the composition was unrenderable: the kit existed,
kit::loweremitted calls to it,splash_renderevaluated them,to_makepad_uimapped them — and nothing ever put the result on a screen. Every layer had tests;
the path had nowhere to end.
splash-ui-l0is a real dependency, so the host realizes and lowers atruntime and the card stays a ledger. The data is baked and static, which is a
real difference from octos-one and is stated in the code.
I shipped five dangling widget names in the commit that added the kinds —
widget_namemapped them toL0TempBarand friends and none existed. Nothingfailed, because the mapping and the definitions are in different crates and
nothing here renders to pixels. The new test walks all three cards, collects
every name this backend emits, and asserts each one the kit owns is defined.
What the kit learned from a phone
Switching octos-one's device path to this kit found four things, in descending
order of damage:
l0_surfacefilled the height and covered the app's ownbackground (94% of the lower half differed, 43.8% → 5.0%); containers with no
stated height rendered at zero, so the stock list came up as four hairlines;
padding and margin had to become asymmetric; and every percentage rendered white
because the tint lowered for
TextStatand notTextValue.Verification
45 workspace tests, 0 failures.
splash-node,splash-render,splash-widgetsand
kit-hostclippy-clean under-D warnings.🤖 Generated with Claude Code