Skip to content

L0: lower to the theme kit, and carry a tap through it - #3

Merged
ymote merged 2 commits into
mainfrom
l0-kit-lowering
Aug 2, 2026
Merged

ymote merged 2 commits into
mainfrom
l0-kit-lowering

Conversation

@ymote

@ymote ymote commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Pairs with OctoSense-org/OctoScript-Makepad#3, which holds the kit and the tests that
execute this contract.

§1.1 says a card names roles and a theme answers them. makepad::lower does
neither: it emits a backend's own widget dialect with ten hardcoded colours and a
font-size ramp, so it decides what a theme decides and reaches one backend of
three.

What lands

kit::lower emits role calls:

l0_panel([ l0_caption("LEAD"), l0_body("Rust 1.95 lands") ])

not  RoundedView{ draw_bg.color: #ffffff12 draw_bg.border_radius: 14.0 … }

A declared tap travels as tapto: "l0:{…}". No VM change and no scoped
state: the renderer's own set: verbs are handled internally and anything else
falls through to the host, so an instance key survives intact. The fork I
described — "collapse L0 events into set: strings" versus "build scoped state
in the VM" — was not a fork. I had not read far enough.

The target is JSON rather than a delimited string, because every field is data:
an instance key contains colons (w:list#0) and slashes, and a payload is
whatever the card bound. Every separator worth using has a data-dependent
failure, and a failure here routes a tap to the wrong row.

hero_points is shared rather than duplicated. Both lowerings size a hero by
what it will DRAW — once a value lowers to a live backend call the drawn text is
not in the DSL at all — and two copies of that ramp would have been two ramps.

What the checks found

The contract is executed in the consumer's repository, and it caught the reverted
attempt's exact defect immediately: Photo with children is the page, not an
image.
The weather card wraps the whole card in one, and lowering it as a leaf
collapsed 62 nodes to 1.

§8 brought current

It claimed the fix "is to retarget it at the component kit", assuming a kit
answering L0's roles existed. None did. It now records what is actually left:
makepad::lower stays because five of the six data visualisations lower to a
marker — not because the kit path is incapable, since live backend calls
already survive it (both lowerings share one value formatter).

It also separates the two senses of "L1" — the capability level this document's
§7 classifies, and the layer above — which that paragraph conflated.

Verification

842 workspace tests, 19 mutation rules held with none unprotected, clippy clean
under --workspace --all-targets --all-features -D warnings.

Not claimed: none of this is on device yet. makepad::lower still drives the
four goldens and the tap loop on the OnePlus 6T, and swapping to the kit path is
the next step, with those goldens as the check.

🤖 Generated with Claude Code

ymote and others added 2 commits August 2, 2026 12:07
§1.1 says a card names roles and a theme answers them. `makepad::lower` does
neither: it emits a backend's own widget dialect with ten hardcoded colours and
a font-size ramp, so it decides what a theme decides and reaches one backend of
three. `kit::lower` emits role calls and lets the kit answer.

    l0_panel([ l0_caption("LEAD"), l0_body("Rust 1.95 lands") ])

    not  RoundedView{ draw_bg.color: #ffffff12 draw_bg.border_radius: 14.0 … }

THE CONTRACT IS EXECUTED, NOT ASSERTED. Two earlier attempts at this were
reverted for asserting one nobody had run — five of twenty-three roles mapped to
tags that did not exist. This crate still cannot depend on `splash-render`, and
must not, so the check lives in `Splash-Makepad`: it dev-depends on this crate
and builds every reference card through the real evaluator. That dependency is
possible only because this crate was extracted from `splash-core` and needs
`serde_json` alone — the extraction paying for itself a second time.

The check found the same defect the reverted attempt shipped, immediately.
`Photo` WITH children is not an image, it is the page: the weather card wraps
the whole card in one, and lowering it as a leaf dropped all 62 nodes to 1.
`l0_surface_photo` composes image, scrim and content, which is what
`makepad::lower` already does and what a leaf cannot.

`hero_points` is now shared rather than duplicated. Both lowerings size a hero
by what it will DRAW, and two copies of that ramp would have been two ramps.

The presentation test asserts the emitted SOURCE, not the built tree: the tree
is full of colours by design, because the kit puts them there. That is the whole
point of the layer.

  842 Splash tests, 32 splash-makepad tests, clippy clean.
  news 25 nodes, stock 11, weather 62 — through the real renderer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An L0 event now reaches the renderer as `tapto: "l0:{…}"`. No VM change, no
scoped state: this renderer's own `set:` verbs are handled internally and
anything else falls through to the host, so an instance key survives intact.
The fork I described between "collapse L0 events into set: strings" and "build
scoped state in the VM" was not a fork — I had not read far enough.

THE WRAPPER IS THE LOAD-BEARING PART. A `card`, `chip` or `image` carrying
`tapto` renders and does nothing: the attribute is accepted by the node model
and then dropped, so the string never reaches the UI at all. Only a container
carries a tap. Measured, not assumed — card, chip and image all produced neither
an overlay nor an on_click, while row, column and text produced one. So a
tappable node is wrapped in a container, uniformly: deciding per role which
kinds carry a tap natively is a second rule that rots the next time this
renderer changes.

The target is JSON, not a delimited string. Every field is data — an instance
key contains colons (`w:list#0`) and slashes, a payload is whatever the card
bound — so every separator worth using has a data-dependent failure, and a
failure here routes a tap to the wrong row.

§8 said the fix "is to retarget it at the component kit", which assumed a kit
answering L0's roles existed. None did; writing it was most of the work. It now
records what is actually left: `makepad::lower` stays because five of the six
data visualisations lower to a marker, not because the kit path is incapable —
live backend calls already survive it, since both lowerings share one value
formatter. It also separates the two senses of "L1", which that paragraph
conflated.

  842 Splash tests, 34 splash-makepad, 19 mutation rules held, clippy clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@r9s-codereview

r9s-codereview Bot commented Aug 2, 2026

Copy link
Copy Markdown

🤖 AI Code Review

Claude CLI exited with code 1:

✅ No significant issues found.


Tokens used: 0 | Powered by r9s.ai

@ymote
ymote merged commit 96a3444 into main Aug 2, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant