Repository navigation
Conversation
The kit has no `import` — it is concatenated as source — so a theme is just a palette prefix. The 15 colours move out of `_kit.splash` into `_palette_dark.splash` (byte-identical values, so the goldens stay green) and three new moods join it: `_palette_light`, `_palette_glass`, `_palette_photo`. Their values are lifted from the shipped weather style templates those moods replace, so a card that used to ask for "minimal weather" and get a hand-authored DSL template lands on the same colours through the kit. The palette must come FIRST, not after: a function body resolves a name at its declaration point, so a palette appended below would not reach the roles that read it. The kit body now says so where the palette used to be, because rendering it alone leaves every colour undefined — which coerces to 0, fully transparent, and draws a card that looks complete. `l0_palettes_agree` pins the four together: every palette must define exactly the names dark does, and the kit must not read an `l0_*` colour no palette defines. That failure is invisible by construction — a missing entry is not a default, it is transparent — and four palettes make it four times as easy. Verified differentially: commenting out one `let` in the light palette fails the test, restoring it passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adopts the structure makepad's own themes use — a handful of dials, everything else computed from them — without taking on makepad's theme module, which the kit's VM cannot reach: `splash_render::build` makes a bare `ScriptVmBase` and is documented as the only makepad-script dependency in the render path. The kit stops hardcoding sizes. `space_factor`, `radius_factor`, `font_base` and `font_step` are the dials; every padding, radius and font size follows from them, and the roles read tokens (`pad_page_x`, `radius_panel`, `font_title`, `weight_bold`) instead of literals. Two things this buys that the flat colour palette could not: NON-COLOUR theming (a compact or larger-text mood is now expressible) and one place to change a scale. A mood is a DELTA now, not a copy. Assembly is `base + delta + _derive + kit`: `_palette_dark` holds the dials and every colour, a mood restates only what differs, and `_derive.splash` computes the sizes afterwards. `glass` is fourteen colours and `radius_factor: 1.5` instead of a second full palette. The ORDER is load-bearing and fails silently both ways. Derivation must come after the delta — `let` evaluates at its own line, so deriving in the base would leave a delta's `radius_factor` with nothing left to change and every mood would quietly keep the base's corners. And an assembly that omits `_derive.splash` leaves every size an undefined name, which is 0: the tree still builds, with no padding and no type scale at all. `l0_cards.rs` was assembling exactly that way after the split and its structural tests still passed, which is the point. Three tests, because none of these failures are visible: - `the_default_tokens_are_the_numbers_the_kit_used_to_hardcode` pins all 20 derived values. This is how "the layer moved, the look did not" is asserted — screenshots cannot do it, since two runs of a card are two generations of it. - `a_delta_can_move_a_knob` builds every mood and asserts glass's panel radius differs from dark's, so the derivation really does follow the dial. - `l0_palettes_agree` now checks that a delta only names tokens the base or the derivation defines: `l0_dimm` would otherwise bind a name nothing reads. `conformance.txt`'s exact node counts are unchanged, so the tree is identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… backend Asserts on the lowered SOURCE, because that is the only artifact where the defect this catches was visible: a card declaring `factor 0.621371` lowered to `(42 * 0.6)` while its realized tree and its built widget tree were both structurally perfect. See Splash's fix for the measurement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ary, by a generator
The experiment: can the material screens' semantic vocabulary be handed to an
LLM as a contract, and a NEW app orchestrated from it? WIDGETS.md is that
contract — written from what the renderers actually read, not from what they
could — and brew.splash was then authored ONLY from the doc, by the generator
the doc is for.
It works: segmented + filter chips + slider + switch list-rows + a derived
summary, a derived price, and a conditional confirmation card, all recomputing
from the store. Verified on desktop at two states — the default ($4.40) and a
seeded one (Large/oat/syrup/3 shots → $6.60, every derived value correct).
The doc caused both failures, which is the finding:
- the module-result trap. `page(brew_body())` as the screen's final
expression evaluates to NOTHING — a blank window, no error anywhere. The
kit's own comment records the rule; the doc advertised page() without it.
Every real screen ends in a literal root, and now the doc says why.
- a helper leak. The first draft used floorn(), which is the flutter kit's,
not this one's — caught before running only because the doc was the
declared boundary and floorn was not in it.
Two harness hooks fell out, both for the unattended QA loop:
- SPLASH_ROUTE: the route file has no desktop equivalent; the env var is it.
- SPLASH_SEED=k=v;k=v — write the store before first mount, so a sweep can
photograph a screen in a CHOSEN state with no input driver at all. The
route file picks the screen; this picks the state.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…land
The 6T rendered brew as boxes with no words: chips, sliders and switches drew
in real M3 and every text node was silently absent. Two causes, one root — the
body was handed as TEXT to the `Splash` isolate:
· `crate_resource("self:…")` resolves against the module that evaluated the
body. On the isolate that is makepad_widgets, which ships no Roboto — so
the font was missing and every label dropped, with nothing anywhere saying
so. The APK even CONTAINED the font (assets/makepad/flutter_samples/…);
it was simply never asked for.
· the emitted `on_click` calls the `NAV` global, which only the app VM
registers — on the isolate every tap died with an unseen "variable NAV
not found".
kit-host hit both and fixed both (its mount comment records the blanked
labels); this ports that fix: evaluate with an explicit ScriptMod carrying
THIS crate's identity, hand the built View to the host widget, queue taps
through NAV/take_tap. Roboto ships in resources/ now, which the packager
already picks up.
Verified on the 6T end to end: the brew screen (authored by a generator from
WIDGETS.md) renders with text, and three adb taps changed size, milk and a
switch — summary and price recomputed to "Large · 2 shot espresso · oat milk,
$5.70", which is the right sum.
Two observations for the QA loop, both device-measured tonight: a tap aimed
from screenshot-estimated coordinates missed by one row (the case for reading
rects from the tree, not the screen), and the scroll swipe started on the
decaf row and toggled it — the swipe-fires-taps class, on this backend too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…k contract
The widget-demo criticism answered with an actual app: Siren Coffee, a
Starbucks-shaped card in the semantic vocabulary. Tabs (Home/Menu/Order), a
seven-drink menu in categories, per-drink customization (Tall/Grande/Venti,
extra shots, oat, whip, quantity), a rewards balance with progress toward the
next free drink, and an order that earns stars. All state derived: the drink
card's "2 in order", the home tab's View-order button, every price, the stars
this order earns, and the balance after placing.
Walked end to end on the 6T by adb taps: customize -> Venti +2 shots oat qty 2
-> Done -> View order (2) · $17.90 -> Place order -> "Order placed, 34 Stars
added · now at 154". Every derived number correct (895¢ × 2, 2 stars/$, 120+34).
What it took is the finding — the WRITE-BACK CONTRACT, now in WIDGETS.md:
· a button with `key` alone is INERT; `tap:1` writes 1, `action:"v"` writes
the literal v — so "back" buttons are `action:"0"`
· a chip writes `!on`; a chip that closes something open must declare `on:1`,
or every tap rewrites 1 into a slot already at 1. The Done chip fired on
every tap for four rounds and changed nothing — the no-op form renders
perfectly and is invisible to everything but a state diff
· sliders on this host never commit (their text editor opens, typed values
do not write through; kit-host's slider-sync was never ported) — segmented
is the index-writer
· listitem action:"switch" renders a switch that is not wired to `key`
· tap targets hit-test in UNSCROLLED coordinates: back/close affordances
belong at the top of the screen
· array `+` is not concatenation and fails silently — push, always
Also: the slider handle is a circle now (SliderRound base) instead of the
full-height stick, per review.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the order view Home/Menu taps from the order view wrote sb_tab correctly and were then overridden: body() consulted a second sb_cart flag first. Two slots both claiming to own the screen is the shadow-state class — the tap fires, the write lands, the screen ignores it. The flag is gone. View-order buttons jump tabs directly (key sb_tab, action:"2"), which the write-back contract already covers: action writes a literal. Verified on the 6T — Home from the order view renders home with the placed order's 154-star balance. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified-by-state-diff, renders-unwired, and does-not-write are three different things that look identical in a screenshot. The contract now says which is which, dated and per-host, and how a widget gets promoted: a synthetic tap that moves its slot, never a look. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One screen where every control writes its own slot and a caption RENDERS that
slot — so a screenshot diff is a state diff, and a control that fires without
writing is visible for the first time. Two proven anchors (chip, button)
validate the harness; the first chip tap missing its 32dp target and reading
as a widget failure is exactly why they are there.
Measured on the 6T, and the ladder in WIDGETS.md is rewritten from it:
✅ writes: chip, button(tap:1/action), segmented, tabs, checkbox
⛔ LIES: radio, toggle, listitem action:"switch" — they flip ON SCREEN,
write nothing, and the next remount silently reverts them. A user
sees their tap work and later finds it undone. This is the worst
class the catalog has, and it is invisible to every screenshot
scorer because the screen genuinely changed.
⛔ inert: fab, dropdown (no key wiring in the renderer at all),
slider (its inline editor opens; nothing ever commits)
🎨 notes: progress draws a ring even with value:; dropdown's caret is tofu
The base-example rule follows from the measurement: exemplars draw from the ✅
row only, and a widget is promoted by a wired-screen pass — a tap that moves
its slot — never by a look.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Six rounds of the on-device movie-box run produced six JS idioms the parser refused — if(cond,a,b), spread, value-position if — because the contract documented the widgets and never the expression dialect. Each rule here is a measured parser failure from that run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add an `image` widget that renders a real bundled photo instead of the
gradient placeholder: `{t:"image", src:"coffee/hero"}` lowers to a native
Image emitting `src: crate_resource("self:resources/<src>.png")` with
CropToFill, full-width by default. A real photo returns None from lower()
so it passes through untouched — a lowered Image that still carried `src`
re-entered the arm forever (same trap the Input-chrome guard avoids).
flutter-samples: self-drive driver (JNI touch injection + framebuffer PNG
capture) so the phone taps and photographs itself; bundle coffee/ resources
(phone-generated hero + cups). WIDGETS.md documents the image row + assets.
Used by the on-device dev-master to author an image-rich coffee app: it led
with the hero, invented a state-derived cup swap, and fixed its own
segmented options:[]->items: bug from a finding.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
l0_tap_fit wraps in a Fit column, but a wrapped text node defaulted to width: Fill, and Fill inside a Fit parent resolves to 0 in makepad — so every tap-wrapped live hero drew as a zero-width column of one-glyph wrapped lines: a small smudge at 17pt, a half-screen invisible void at 43. Found the moment the hero-size fix made live heroes big enough to see they were not there. Coerce the child to fitw so it hugs its glyphs exactly like its wrapper. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwB6ZiXeW6K8AiKCnJvJ98
Two items off the mockup-translation gap list (NOTES-weather-tokyo.md): - The photo surface's scrim is two stops instead of one flat wash: l0_scrim_top over the photograph, the mood's l0_scrim under the content. The photo mood restores the shipped immersive exemplar's exact stops (#0a0f1a22 -> #05070cf2) — flattened to one colour when the style was ported into L0, and most of why a forecast panel never read as floating out of the dark. - weight_hero: the hero gets its own weight axis. Base stays bold (700); the photo mood dials it to 100 — a hairline numeral over a photograph, the largest visual gap between the shipped hero and the design mockup after size itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwB6ZiXeW6K8AiKCnJvJ98
…ons, pane inset Closing the measured gap between the HTML twin (8/10 vs the mockup) and the Splash card (4/10 at the start of this round, 6/10 after): - hero_factor scales only the hero ramp's TOP step, so a photo-surface numeral reaches mockup scale without a long text hero wrapping at 86pt - l0_bar / l0_bar_rail: the mood hands TempBar one hue on a faint rail - icon_mono: WeatherIcon recoloured to a single ink via l0_icon_ink — composed around the constructor like l0_tinted, because mutating a fresh dict's w/h evaluated to nil in this VM - panel_inset: the floating inset card; l0_fill goes dark-translucent, hairlines quieter, weight_semi 500 in the photo mood Remaining, measured and unreachable from a mood: letterspaced caps and a real blur primitive (pipeline-v2/REPORT.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwB6ZiXeW6K8AiKCnJvJ98
The kit's answer to TextEyebrow (splash 'TextEyebrow' role): caption scale, medium weight, soft ink, and a variant marker the backend uses to space and uppercase a literal. Photo-mood value weight drops to 400 per the parity judge's 'rows bolder than the mockup'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DwB6ZiXeW6K8AiKCnJvJ98
…s not land yet
row_lines: l0_row_text stamps `lines` from the palette, so whether a list
row truncates is the theme's call and no card names it — presentation
staying where profile 1.1 puts it. Base is 1: the round-2 corpus showed
titles wrapping to three lines and breaking mid-word ('Thund erstorm'),
which wrecks the row rhythm on every feed card.
panel_border/l0_stroke: l0_panel now carries border and bordercolor, and
the light mood asks for a hairline edge — a panel with no edge dissolves
into a light page, which is what the material and pastel schools were
marked down for. This does NOT currently reach the screen; see the note
in the app emitter.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DwB6ZiXeW6K8AiKCnJvJ98
`Attrs.elevation` has been declared on the node and read by the evaluator the whole time, and written by nothing. `l0_panel` now carries it, and the base palette gains `panel_lift` — 0 for dark, which separates by fill alone, and 6 for light, where a card on near-white needs a shadow to read as a surface. Delivery-gated on device 2026-08-30: the light baseline now darkens the page by 44 luminance levels just below the card edge (199 against a 243 background). Also fixes a scope error that fired on EVERY render: `l0_stroke` read `l0_hairline` 27 lines before that name was declared. `let` evaluates at its own line, so the name was unresolved, and an unresolved name coerces to 0 — a fully transparent stroke ink. The declaration now sits after the colour it reads, and the device log goes from a scope error per render to none.
The weather fixture writes `Col(gap: 36)` with a comment calling it "the mockup's air — the half-screen of photograph". Over the photo mood that gap frames a picture; over a flat fill it is simply a hole, and the same card scored 6/10 in photo against 5/10 in the three flat moods, with every mood's judge naming the void as its biggest gap. `l0_col_gap` is the one place the kit can still speak about a card-authored gap, and §1.1 says spacing is the kit's to choose. Gaps of 24 or more are scaled by a new `air_factor` — 0.62 in the base, 1 in photo, which keeps the picture's air whole. Anything under 24 is deliberate fine spacing between related things and is untouched. Measured on device: the mid-screen void between the hero and the forecast card drops from 164px to 126px. Written as an early return rather than reassigning a local inside the `if`. The first version did `let s = g` then `s = g * air_factor` in the branch and moved the rendered void by exactly nothing, so that mutation does not escape the block — `l0_colw` already branches by returning, and this now matches it.
Two knobs already existed and only `photo` carried them. Scoring the four moods turned that into a natural experiment twice over: photo was the only mood NOT accused of "system emoji icons instead of custom iconography", and later the only one NOT accused of "spectral gradient bars that break the restrained palette". The three moods at the default were flagged both times. `icon_mono` and `l0_bar`/`l0_bar_rail` now sit in the base. Light overrides the rail, because the base's white-at-16% vanishes on a near-white page. The rainbow ramp also encodes nothing the bar's own length does not already carry, so the spectrum was decoration competing with the type.
A judge scoring these renders called the flat moods "generic flat surfaces" with "no atmosphere", and scored the photo mood — the only one whose page is not a flat colour — on its atmosphere rather than against it. `l0_surface` now carries `bg2`, the same second stop the photo scrim already uses, so this applies an existing and verified capability where it was missing rather than adding anything new. Dark settles a few levels darker toward the bottom, light lifts toward white, glass deepens into its navy. A mood that wants a genuinely flat page sets `l0_base_2` equal to `l0_base`.
The first version lifted light's page to #ffffff -- exactly l0_fill, the card's own colour -- so the card dissolved into the page toward the bottom of the screen. Measured: page and card both at luminance 255, separation ZERO. A judge scoring that build called it 'near-white' with 'flat low-contrast surfaces', which is precisely that merge; I read it as a generic complaint rather than as the regression I had just introduced. Now deepens to #e8e8f0. Separation 0 -> 21. A gradient behind a surface has to move away from that surface's fill.
Both stops sat within five levels of each other, so the fall rendered as 5 luminance levels on device and a judge called the background 'flat black' and the gradient 'invisible' -- correctly. Glass, whose stops are 39 levels apart, reads as intended. The TOP stop lifts to #131a25 and the BOTTOM stop becomes the canonical #0a0e14, so the screen still settles on the page colour where content ends. Measured fall: 5 -> 11 levels.
One indigo, lifted from the photo mood into the base, cannot serve a navy page, a white card and a near-black page at once. Measured against each mood's own panel: glass 1.76:1 and light 1.92:1 -- the 'accent bars clash' and 'accent colour feels generic' a judge named -- while dark sat at an acceptable 2.90:1. Glass goes lighter (#9fd8ff, 2.76:1) because on navy the separation has to come from luminance; light goes deeper (#3d48b8, 2.96:1) for the same reason against white. Dark keeps the base indigo. Both landed on their predicted values on device. This is not accent VARIETY, which measured null earlier -- it is contrast against the surface, which is a different quantity.
pad_page_top was 54 logical px with a comment saying it clears the status bar. Measured on device, the system already insets the app below the bar -- the page's own fill begins at y=102, under a black status area -- so all 54 sat inside already-safe space and rendered as the screen's single largest void (173px, 7.4% of the display). 36 rather than 0: a 24dp status bar still clears with margin if some window configuration ever stops insetting. Measured: first ink moves from y=272 to y=221, the top band falls 166 -> 116px, the trailing band disappears, and total dead vertical space goes 487 -> 394px.
The previous commit made the fall visible by raising the page's TOP stop to
#131a25. It worked (fall 5 -> 11) and paid for it exactly where it hurts: the
hero's contrast against the page went 11.0:1 -> 8.0:1, and a judge immediately
called the text contrast low.
Deepening the BOTTOM to #000103 instead is strictly better on both axes:
original flat fall 5 hero 11.0:1
lifted top fall 11 hero 8.0:1
deepened bottom fall 11 hero 12.5:1
The top is where the type sits; the bottom is where the content has already
ended. A page gradient should spend its range there.
This file's own comment says 'the scrim carries all of the legibility'. At 13% opacity at the top it did not: the hero numeral measured 2.59:1 against the photograph — under WCAG AA's 3:1 for large text — and 2.71:1 even before the top-padding change moved the hero into a brighter part of the image. A paired judge preferred an older build purely because its hero happened to sit over a darker region, which is legibility by luck. Top stop raised to 40%. Measured on device: 2.59 -> 3.37:1, AA pass, and better than the build this regressed from. 40% rather than the ~65% that would reach 4.5:1: past that the photograph stops reading as a photograph, and this mood exists to show one.
The hero measured 2.59:1 against the photograph, under WCAG AA's 3:1 for large text, so the previous commit raised the top scrim to 40%. That reached 3.37:1 and flattened the upper half of the image into uniform grey; a paired judge preferred the OLD build for exactly that reason -- 'keeps the photo's depth and lantern glow alive' -- and it was right. This mood exists to show a photograph. The scrim was the wrong lever. weight_hero was 100, the lightest weight there is, over a busy image: the strokes were thin, not merely low-contrast, which is why WCAG sets a lower threshold for bold text. 200 keeps the cinematic thin hero and puts down 28% more ink than the original (5138 px vs 4011) -- MORE than the 40% scrim managed (5066) -- while the scrim pulls back to 22%. Paired judging prefers this over BOTH the original and the heavy scrim. Honest limitation: the luminance ratio is 2.79:1, still under AA. Reaching 3:1 by luminance alone costs the photograph, and the two goals genuinely conflict here. Recorded rather than hidden.
43 corpus cards declare one of these two and could not realize. The catalog entry lands in Splash; this is the kit's answer to the names. Both are deltas from dark and both keep the panel-lift ratio every shipped mood converges on — measured at 1.12:1 in light and dark alike, and stated here as an alpha so it self-scales to whatever ground the mood sets. `vibrant` puts the colour in the PAGE rather than in an accent, which is the one thing the accent axis cannot do: accent reaches a bar, a highlight and a button and never a surface. Ink stays near-white — tinting the text as well is how a vibrant mood becomes an unreadable one. `minimal` withdraws colour entirely and lets a hairline do the separating. Its brief is restraint, and one measurement this session says what restraint must NOT mean: in an eight-way blind comparison the emptiest mockup lost all seven of its pairs, with both judges calling its space "void", "empty, not a pause", "unmotivated". So the budget goes into a wider ink range and a slightly airier rhythm, not into removing things. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`light` is the only mood allowed to invert `l0_text`, because the host's light-over-Photo substitution keys on exactly that being the single case. A second mood setting a dark ink would render unreadable text over a photograph with every automated check still passing — and `l0_card.rs` has a test that says so, which caught this before the pointer moved. Both moods wanted dark's white anyway, so nothing is given up. The reason is recorded beside each, since the next mood added here will hit the same wall. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
… library _derive_color.splash computes the palette from nine seed scalars in-kit via mod.math.round — the Ant-style seed architecture; gen_grounds emits 14 grounds as seeds, not solved files. Packs: _palette_atro(+_light) carry a purchased kit's system whole (palette via the E4 solver, measured type scale at the 0.62 font-unit calibration, navy glow depth, geometric family, card gradient). Axis fragments: accent x mood cross-product (incl. packs), depth (flat/hard/ glow bound to solved tokens), type (serif/display), texture (grains+patterns). Kit: l0_card splits from l0_panel for pack gradients; l0_avatar; texture/ family/tracking ride every text role. splash-node Attrs grows texture/shadow/ family/gradient_across fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in layers l0_surface goes fith->fillh (a design frame is owned, not hugged), l0_thumb takes a square flag, and l0_surface_pin2/pin3 place layers by aligny because deferred fills in the app fork are greedy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o ground Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… attr Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sixteen fragments, eight hues over the pack's two moods, minted the same way the other packs' are. Without them a CaMo card naming an accent found nothing to splice, and an axis the assembler cannot find contributes silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_derive_color` reads `l0_accent`'s previous binding, and nothing bound it unless a card named an accent AXIS. So every base mood — dark, light, glass, minimal, vibrant, photo — errored once and painted the accent fully transparent, which is invisible because 0 is a legal colour and the VM's complaint goes nowhere this side can read. The value is not invented: `l0_bar` in this file IS the base's accent, and an accent fragment says exactly that (`l0_bar = l0_accent`). A fragment spliced later still overrides it. Found by `lab/sketch/lint_theme.py`, which resolves the chain statically; confirmed independently by running the VM. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pack's card colour is part of its identity — CaMo's black slab, Atro's indigo gradient — and it is stated once, in the pack's base palette. The `_light` variant inherits that colour and flips `l0_text` dark with it, and nothing re-solves the pair: CaMo light drew near-black headlines on a pure black card at 1.14 contrast, unreadable, on the desktop rail and the phone alike. That both rails agreed is what proved it was the theme. The kit used to mark such a card `inkdark`. No renderer ever read that flag and the evaluator never even parsed it, so it fixed nothing for as long as it existed. `ink` carries the colour instead, and a backend applies it only where a descendant's own colour fails contrast against the card — an accent that already reads keeps what the theme gave it. `l0_aligned` also records why the two rails still disagree about centring a column: giving it `fillw` does not close it, because a makepad Label defaults to Fill width while an ArkUI text node hugs, so every child spans the centred column and still draws its glyphs at its own left edge. Measured, not assumed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Attrs.inkdark` was a boolean marking "this card's fill needs different text". No backend read it, and `splash-render`'s evaluator never parsed it out of the VM at all, so a card could set it for months and change nothing. A flag cannot say WHICH ink; carrying the colour can, and lets a backend apply it only where it is needed. `splash-makepad`'s emitter resolves the planes as a pre-pass over the tree rather than threading them through emission: it is a property of the tree, not of the emission, and the two backends then apply one rule at the same point in their pipelines. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`dc_toward_ink` and `dc_toward_pole` were defined ABOVE the accent-as-seed solve and read `seed_ink_*`. A function in this VM keeps the binding a name had when the function was defined — the boot selftest on the ArkUI rail now logs that answer rather than leaving it to argument — so both mixers used the ORIGINAL seeds and never saw the tinted values the solve produces. Every ink derived through them (l0_text, l0_soft, l0_dim) came out untinted, and the whole accent-as-seed path was inert. A call may precede a definition, so moving the two definitions past the rebinds is the entire fix. Found by `lab/sketch/lint_theme.py`. The light mood's `l0_dim` was 2.92 against its own page — below AA and below even the large-text floor, so every caption in that mood was under-contrast. Nothing measured it because the auditor checked the PRIMARY ink only, and captions do not use the primary ink. Darkened along its own faint blue cast until the page pair clears AA at 4.63. Neither change reaches the corpus: none of its 122 cards names `theme light` or declares an accent axis, and the 15 Atro screens re-render pixel-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rces Work in progress carried by the Octoscript-AppCard l0-content-parity branch: the native L0 kit packs (atro/camo/taskplan/ux, with the fonts their tokens reference), beauty/kit-audit examples, design.rs and l0.rs in splash-makepad, kit/design/progress widgets, chart_paint in makepad-plot, l0_helpers in splash-render, units in splash-node, and the native-l0-kits doc. Makepad studio state (.makepad/) is ignored. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # README.md # apps/flutter-samples/Cargo.toml # apps/flutter-samples/src/lib.rs # apps/kit-host/Cargo.toml # apps/kit-host/src/l0.rs # apps/kit-host/src/main.rs # apps/kit-host/src/screens.rs # components/l0/conformance.txt # crates/makepad-d3/Cargo.toml # crates/makepad-d3/README.md # crates/makepad-plot/Cargo.toml # crates/octoscript-makepad/Cargo.toml # crates/octoscript-makepad/src/lib.rs # crates/octoscript-makepad/tests/l0_cards.rs # crates/octoscript-makepad/tests/l0_kit.rs # crates/octoscript-render/Cargo.toml # crates/octoscript-render/src/eval.rs # crates/octoscript-render/src/lib.rs # crates/octoscript-widgets/Cargo.toml
The l0-photo-weight tip was built against a local makepad checkout that has `ScriptVm::eval_checked` and the pre-ScriptVmHost VM shape; the workspace now pins OctoSense-org/makepad @ 8c23d57, which has neither. Build the VM the way main already does (`ScriptVmHost::new((), ())`) and express `eval_checked` in octoscript-render with the pinned revision's `with_instruction_limit` and captured-error sink, so octoscript-render, octoscript-makepad and octoscript-node build and test against the pin. The kit-host beauty preview calls the same helper.
Collaborator
Author
|
Superseded by #18, which merged l0-photo-weight into main under the new crate names with the makepad pin at 703c00a (the fork now carries the four widget/svg APIs this branch needed). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch is
l0-photo-weight(45 commits: the L0 native kits, beauty checks, design tokens, kit-host resources, ~224k lines of generated fragments undercomponents/l0) with the same mechanical Splash -> Octoscript rename thatmaincarries in fa91cae + a96f24b applied on top as one commit, thenorigin/mainmerged in (which brings the[workspace.dependencies]pinning from d49892a/6e2851e/844905f), plus one small port commit described below.Rename tooling. The same
rename.shthat produced main's rename, unchanged (263 pathsgit mv'd, 124 files rewritten), followed byfix-makepad-names.plandfix-makepad-docs.plover every tracked file to restore the identifiers upstream makepad owns (CxSplashVmExt,SplashVmId,MAIN_SPLASH_VM_ID,alloc_splash_vm_with_network,inject_splash_ui_handle,mark_splash_isolate_dead,handle_splash_network_responses,register_splash_isolate_mod, and the makepadSplashwidget inlive_design!and docs) — the same follow-up as a96f24b.Merge. 19 conflicting files, all on lines the branch had changed that also carried the old name. Rust/DSL/docs (
README.md,apps/flutter-samples/src/lib.rs,apps/kit-host/src/{l0,main,screens}.rs,components/l0/conformance.txt,crates/makepad-d3/README.md,crates/octoscript-makepad/src/lib.rs,crates/octoscript-makepad/tests/{l0_cards,l0_kit}.rs,crates/octoscript-render/src/{eval,lib}.rs) took the branch's renamed side. The seven Cargo manifests (apps/flutter-samples,apps/kit-host,crates/makepad-d3,crates/makepad-plot,crates/octoscript-makepad,crates/octoscript-render,crates/octoscript-widgets) took the branch's side and then had everymakepad-widgets/makepad-script/octoscript-ui-l0line converted from the branch's sibling-checkout paths (../../../makepad/...,../../../octoscript/...) to{ workspace = true }; the root[workspace.dependencies]block from main survived the merge untouched. No new manifest was added by the branch. After the merge, the only non-manifest differences from the rename commit are main's two..Walk::default()lines.octoscript-ui-l0pointer. The root manifest pointsoctoscript-ui-l0athttps://github.com/OctoSense-org/Octoscript.git,branch = "rename/fix-language-review-20260904"(OctoSense-org/OctoScript#20) instead ofmain, because the L0 node model this branch needs is only on that branch. Flip it back tomainonce that PR merges.Port commit (
Port octoscript-render's evaluator to the workspace makepad pin). The branch tip does not build against the pinned makepad 8c23d57:octoscript-render/src/eval.rsconstructs the VM with the pre-ScriptVmHostshape (std: &mut std_slot) and callsScriptVm::eval_checked, which exists in no published OctoSense-org/makepad revision (it appears only onport/appcard-on-octoscript@ 5b6932cf, and that lineage has the new VM shape). The commit builds the VM the way main's own eval.rs does and addsoctoscript_render::eval_checked(same contract, written withwith_instruction_limit+ the captured-error sink);apps/kit-host/src/beauty.rscalls it. It is a separate commit so it can be dropped or replaced if a makepad revision carryingeval_checkedgets pinned instead.Verification.
git grep -n -i splashminus the protected spellings: onlyapps/kit-host/src/beauty.rs:19 host := Splash { width: Fill height: Fill }, which is the makepadSplashwidget mount (kept on purpose, same as main'sSplash{ ... }mounts; the exclusion pattern just lacks the space). No path contains the old name.cargo test -p octoscript-render -p octoscript-makepad -p octoscript-node: 70 passed, 0 failed — including the L0 card tests that pull the fixtures andoctoscript-ui-l0from the renamed Octoscript branch.cargo check --workspace --all-targetsdoes NOT pass:octoscript-widgets(18 errors:Widget::selected_valuex7 andWidget::checkedare not trait members,Viewhas no fieldselectedx10) andmakepad-plot(3 errors:GradientStophas no fieldstraight_rgb) use makepad APIs that exist in no published makepad revision (checked 8c23d57,port/appcard-on-octoscript,work,dev,main,octoscript-vm-hardening, octos-orgoctos-one-framework, upstream makepad dev). The branch tip was evidently built against an unpublished local makepad. Resolving that needs either that makepad work pushed and pinned, or those two crates ported — outside the scope of a rename PR, so it is left as is and called out here.include_str!L0 fixtures from a sibling checkout../octoscript/crates/octoscript-ui-l0/tests/fixtures/(lower-case, as the branch had../splash/; main spells it../Octoscript/) — an out-of-repo dependency inherited from the branch.🤖 Generated with Claude Code
https://claude.ai/code/session_01JgSir4cQyaakzPju4h6smw