Skip to content

Run Flutter widget code on Codename One - #5883

Open
shai-almog wants to merge 329 commits into
masterfrom
flutter-dart-transpilation
Open

shai-almog wants to merge 329 commits into
masterfrom
flutter-dart-transpilation

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Compiles Dart widget source to Java at build time and runs it on Codename One's own renderer. There is no Dart VM in the result, no embedded engine and no platform view — a Dart screen becomes ordinary Codename One components, so it inherits the theme, the event thread, the accessibility tree and the native build.

Two entry points, which are the two things a user actually wants to do:

  • FlutterUI.wrap(widget) returns a Container that goes anywhere an ordinary component does, for putting one Dart screen inside an existing app. It deliberately does not install the Material base theme, so it will not restyle the screen around it.
  • FlutterUI.runApp(widget) mounts the tree as the whole UI, and does install it.

The build wiring is the archetype's own: the transcode-flutter goal is already bound and is a silent no-op until src/main/flutter exists. No Dart SDK is involved — the transpiler is Java and the Dart is input, never executed.

Benchmark

scripts/flutter-bench builds one application two ways — the Flutter toolchain's release build, and the identical Dart source transpiled by Codename One — and publishes size, start-up and idle memory per platform to the PR and to port status.

Neither application is vendored. prepare.sh takes the gallery from the Flutter SDK CI clones and copies the same 159 files into both trees, so the comparison cannot drift: there is no second copy for an edit to land on. An earlier round of this work compared two different galleries and every number it produced was meaningless. The Codename One side is generated from the shipping archetype with the runtime dependency enabled — the two steps the guide tells a user to take — so a change that breaks the documented wiring breaks the benchmark too.

Three measurement decisions, each because the obvious alternative flattered us:

  • Start-up is a bracket. Flutter's FIRSTCONTENT is a UI-thread callback that runs before that frame is rasterised, while our FIRSTFRAME fires once the form is on screen. Comparing them charges one runtime for rasterising its first screen and not the other — which is what the harness this replaces did. Flutter's figure is reported as a range and the ratio uses the end least favourable to us.
  • Executable code is every Mach-O in the bundle, not the main executable. On iOS a Flutter application's own code is not in the executable at all: Runner is a thin launcher and the Dart image sits in Frameworks/App.framework. Sizing the executable compared our whole runtime against their stub.
  • Assets are staged from Flutter's own built bundle. Staging the whole asset package ships files Flutter tree-shakes away; staging only the 1x images ships fewer than Flutter does.

And two refusals rather than a substituted number:

  • iOS start-up and memory are not measured. Dart cannot AOT-compile for the simulator, so a simulator run would time Flutter's JIT debug engine against our release build. Sizes come from release device bundles, which need no signing.
  • Desktop is the native ports only — AppKit, clang-cl, GTK3/Cairo — never JavaSE, whose bundled JVM and dependency directory are not the same kind of artifact as Flutter's native bundle.

Nothing contacts the build server: the *-source and local-* targets build on the runner. Only our own numbers are gated, so a Flutter SDK upgrade that grows their build cannot turn ours red.

Fidelity work in this PR

  • RoundBorder draws itself when there is no shadow and no uiid mode, instead of going through a component-sized offscreen image. The cache hides the cost for a static shape; for one that animates its size it does not — a circle growing to 1618×1618 threw away a 10MB surface per frame.
  • A paint-only shift needs a box it can paint into. Component.paintInternalImpl clips every component to its own rectangle, so FractionalTranslation drew most of itself into the discarded region — the feature-discovery circle rendered as a quadrant with two straight edges meeting at its centre.
  • FittedBox was a pass-through. Flutter lays its child out unbounded and scales the result, which is why text inside one shrinks instead of wrapping.
  • Card rebuilt its border every time, and RoundRectBorder caches its shadow against the border instance, so every rebuild re-rendered it with a gaussian blur. With the cache off it also translates the live Graphics by the shadow offset and never undoes it, so cards drifted 9px down and 4px across, accumulating down the page.

Verification

  • 398 flutter-runtime tests, plus 21 new tests for the benchmark's arithmetic
  • Parity sweep 48/48 routes: device mean 2.06%, median 1.66%; /demo/card 14.98% → 2.30%, /demo/grid-lists 26.30% → 0.32%
  • prepare.sh verified end to end; the generated project builds 563 Java files from 159 Dart files, which also exercises the documented user path
  • actionlint 74/74, copyright and control-character gates clean against the merge base

Not yet proven

No benchmark platform adapter has been exercised end to end — run_bench.py --list reports that rather than implying otherwise, and the first CI run is the thing to review. The native compile steps (xcodebuild, gradle, clang-cl, GTK3) have not run on a runner.

This is also the first mention of the feature in docs/, which has carried none until now.

shai-almog and others added 30 commits September 18, 2026 21:40
…t check out

The buffer-backed upload -- making the texture a view onto an MTLBuffer so
CoreGraphics rasterises straight into the memory the GPU samples -- is REVERTED.
A pixel-level check of the resulting texture against CoreGraphics found regions
reading back as ZERO, unwritten: 1554 and 45856 pixels of a 560x384 image, on
two reads out of eighteen. CGContextFlush before creating the texture did not
fix it, it only made the clean runs more common -- three consecutive clean runs
preceded the two that failed, which is exactly why one clean run proves nothing
here.

The cause is not pinned down (CoreGraphics deferring into a linear destination,
or getMTLTexture racing another thread), and it does not need to be: the failure
is intermittent, silent, and corrupts what is DRAWN as well as what is read
back. An occasional torn image in a shipping port is not worth a memory
optimisation that could not even be measured on this machine. replaceRegion
copies the bytes out through CoreGraphics' own accounting and has never shown a
hole. The finding is written into the comment where the next person will look.

The getRGB texture reader and the shared-storage revalidation skip go with it --
both only ever applied to a shared-storage texture, which no longer exists.

Kept, because both were verified:

Image.createImage(int[], w, h) now premultiplies in ONE PASS. It used to wrap the
caller's array in a CGImage, malloc and zero a second full-size buffer, draw the
first image into the second through the whole CoreGraphics pixel pipeline, and
make a third CGImage out of the result -- to multiply three bytes by a fourth.
This is on the start-up path: it is how the runtime rounds the corners of every
card image. Premultiply is rounded rather than truncated, or every
semi-transparent pixel drifts a level darker. CN1_VERIFY_ARGB reproduces the old
conversion and compares pixel by pixel: 36, 24 and 36 images across three runs,
zero differing pixels.

Mutable-image readback stages only the rect that was asked for. It used to
allocate a scratch texture the size of the whole image, blit the whole image
into it and malloc the whole image again, in order to return a sub-rect: a
100x100 getRGB on a full-screen mutable moved 12MB three times to deliver 40KB.

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

The iOS port kept the UIImage behind every picture for the life of the peer, so
it could re-upload the texture after iOS discards it during a suspend. That made
each on-screen picture resident TWICE -- CoreGraphics' decoded raster on the CPU
plus the texture on the GPU -- which is the whole of this port's "CG raster
data" against a competing toolchain's zero.

It was never needed for an EncodedImage. That class already holds the encoded
bytes and already re-decodes on demand; it is a complete recovery path, and the
UIImage was a second one duplicating it.

So EncodedImage now decodes through createImageNoBackingCopy, and the iOS peer
releases its UIImage the moment the texture exists. Recovery is a static
generation counter: applicationDidEnterBackground bumps it, and each instance
compares its own generation the next time it is asked for pixels, dropping the
stale decode and decoding again from the bytes it kept.

A counter, deliberately, not a registry and not a sweep. Nothing is walked and
nothing is touched at suspend; an image that is never used again is never looked
at. A locked image keeps its stale decode until it is next asked for, and then
discards it unused -- so locking cannot pin a decode the platform invalidated,
and the lock protocol is not disturbed. Width and height are NOT reset, because
they are a property of the encoded bytes, which have not changed; resetting them
would make every layout that measured the image wrong until it re-decoded.

The peer also stops revalidating itself on the texture-validate generation when
it has no backing copy: that recovery is both impossible (nothing to re-decode
from) and unnecessary (its Java owner rebuilds the whole image, peer included).

Default createImageNoBackingCopy is plain createImage, so every port that never
kept such a copy is untouched.

core-unittests: 5214 pass, including four that pin the generation -- a decode is
cached when nothing invalidates it, invalidation forces a re-decode, it reaches
locked images without breaking the lock, and it leaves dimensions alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bump was on applicationDidEnterBackground; the texture drop is on
applicationWillResignActive -- cn1ApplicationWillResignActive calls
CN1MetalBackupMutableImagesForSuspend, which drops the texture of every
read-only image.

Resigning active happens far more often than backgrounding and does not imply
it: Control Centre, the notification shade, an incoming call, the app switcher,
a system alert. Each drops the textures and hands control straight back without
the app ever entering the background. An image created through
createImageNoBackingCopy has already released its decoded UIImage, so in that
window it had no texture, no UIImage, and nothing telling it to rebuild -- it
would have drawn blank.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Memory at rest 116.1MB against 87.6MB, 0.76x from 0.45x when this work started,
and the run-to-run bimodality that made every earlier figure unquotable is gone:
four consecutive launches settle within 6MB of each other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cn1StartupPhase prints elapsed-since-process-start -- measured against the
KERNEL's record of process start, not a mark taken inside the app, because the
interesting part of a slow launch is what happens before any of our code runs.
Gated on CN1_STARTUP_PHASES, so a shipping build pays one cached getenv. Probes
at main (initConstantPool), at UIApplicationMain, and at didFinishLaunching.

The reason for it: two trivial C programs, identical source, one native macOS
and one Mac Catalyst, timed launch to main best-of-15 -- AppKit 6.6ms against
Catalyst 19.4ms, with Catalyst loading 1565 dylibs to AppKit's 738. So Catalyst
costs about 13ms and that is now measured rather than assumed.

It also kills a hypothesis: adding all 26 of the frameworks this port links took
that same trivial Catalyst binary from 19.4ms to 20.8ms. The framework list is a
real difference against the competitor's and an irrelevant one; Catalyst already
pulls everything.

And it reframes the rest. Codename One reaches main at the same ~20ms any
Catalyst app does, so the ~97ms this branch had been calling pre-main overhead is
mostly AFTER main -- VM init, UIKit bring-up, Display.init -- about 77ms of our
own initialisation, three times the size of the gap being chased, never
decomposed until now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CN1_GPU_TIME reports what the GPU actually spent on a frame, from the command
buffer's own GPUStartTime/GPUEndTime in a completion handler. This is the number
vsync cadence cannot give you: a renderer presenting at a steady 60fps looks
identical whether it used 2ms or 15ms of the 16.67ms budget, and the difference
is exactly what decides whether there is room to repaint more.

The question it exists to answer: the offscreen screenTexture costs a fixed
full-screen texture -- 12MB, about 43% of the remaining memory gap against the
competitor -- to allow partial repaint. CN1_REPAINT_RATIO, with the application
properly foregrounded, says the application repaints 64-86% of the screen per
frame, so partial repaint is saving only 15-35% of the pixel work. On the face of
it that is a bad trade, but removing the texture makes every frame a full repaint
and nobody should do that without knowing the GPU has the headroom.

Two traps this work uncovered, both worth knowing before trusting any rendering
measurement on this platform:

Launching the binary from a shell leaves it reporting as BACKGROUNDED, and
drawFrame returns early there, so the renderer never runs. Use
`open -a <path>.app`. bench_fps keeps reporting ~60fps either way because it
reads the Java-side EDT counter, not GPU frames -- a terminal-launched fps figure
is EDT cadence, not rendering throughput.

And both applications use meaningfully more memory when active: Codename One
128.8MB against 148.4MB, the competitor 100.0 against 114.6. The comparison
survives, since both sides are measured identically and move similarly, but the
absolute figures describe an idle unfocused application rather than one a user is
looking at.

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

The merge of origin/master left this function unbuildable: it declares `pixels`
from the branch's one-pass premultiply and then goes on to use `context`, `iref`
and `provider` from master's version, none of which exist. iOS and macOS have not
compiled since -- the failure is five "use of undeclared identifier" errors in the
translated C, so it surfaces at the app build rather than in this repo.

The two halves are alternative implementations of the same conversion, so the fix
is to take one of them whole. This takes the branch's, which the merge otherwise
loses: Codename One hands over straight (un-premultiplied) ARGB and CoreGraphics
wants it premultiplied, and master's way of getting there wraps the caller's array
in a CGImage, mallocs a second full-size buffer, memsets it, wraps THAT in a bitmap
context, draws image one into image two through the whole CoreGraphics pixel
pipeline, and makes a third CGImage of the result -- to multiply three bytes by a
fourth. Image.createImage(int[], w, h) is on the start-up path (it is how the
runtime rounds the corners of every card image), so that ran once per picture
during the first frame.

The single pass also has to round rather than truncate: CoreGraphics rounds, and a
truncating premultiply drifts one level darker on every semi-transparent pixel.

Channel order and premultiply rounding are invisible in a stack trace and show up
as an image that is subtly dark or blue, so the conversion carries its own check:
CN1_VERIFY_ARGB reruns the old CoreGraphics path and reports any pixel that
differs. Verified against a 256x256 image covering every alpha 0-255 against a full
colour sweep -- all 65,536 pixels identical, "CN1_VERIFY_ARGB: 256x256 matches".

cn1ArgbImageFreeData comes back with it: the provider owns the premultiplied
buffer and frees it through that callback, and it must be a C function pointer, so
it lives at file scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured on a native arm64 build of a transpiled Flutter Gallery against the
real Flutter build of the same app. CPU to first frame fell from 252ms to
175ms across this and the two commits that follow.

Rendering: the CAMetalLayer is replaced by a plain CALayer fed from one
IOSurface-backed MTLTexture. The layer's drawable pool has a minimum of two
buffers that a framework which owns its own framebuffer never needs, and
handing the surface over from addCompletedHandler instead of
waitUntilCompleted takes a 16ms stall off the first frame.

Window: the build is queued on the main queue before [NSApp run] so it
overlaps VM boot rather than following it, the primary display scale is
published before the window exists so the first layout does not have to wait
for one, and macMonitorForMainWindow no longer builds a window just to answer
a question about monitors. displayWidth/displayHeight return a default size
when no window exists yet; a size change is delivered later, and the extra
layout that costs is cheaper than the stall it removes (reverting it cost
15ms).

Fonts: registering all 33 bundled font files up front is replaced by
registering the single file a font resolution actually names, with the
whole-bundle scan kept as the fallback for a name that will not resolve.

Instrumentation: cn1StartupPhase() markers behind CN1_STARTUP_PHASES (one
cached getenv) report where cold start goes, including the VM's constant pool.
They are what showed that the constant pool costs 1ms, that the window costs
41.8ms of which 27.4ms is inside AppKit's own initWithContentRect:, and that
an earlier 46.7ms "present path" was machine load rather than a real cost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
collectAttached recursed by handing visitChildren a fresh capturing callback
at every element, even though what it captured -- the attached set, the output
list, the enclosing element's host -- is identical at every level. An
allocation census counted 6,443 of those callbacks on one screen, the largest
anonymous-class count in the runtime. One collector now serves the whole walk;
it holds no per-node state, so reusing it down the recursion is safe.

ImageRenderElement read a PNG/JPEG/GIF header for its dimensions instead of
decoding the image to answer getWidth(), so an image that is only measured is
never decoded. That is 38% off simulator start-up (402ms -> 262ms in
invokeMain) and near zero on a native build, where decoding was already cheap.

RenderElement decides per UIID, keyed on the theme generation, whether a
component needs its Codename One styling neutralised, instead of asking per
component; FlutterUI emits zero margins for the Flutter UIIDs up front so four
of eleven stop needing the treatment at all.

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

Reproduces the Java semantics the workloads depend on rather than the
idiomatic Dart equivalent -- 32-bit wrapping arithmetic, Java's unsigned shift
and Java's String.hashCode -- so the two runtimes are doing the same work. All
eleven checksums match the Java side bit for bit, which is the precondition
for the ratios meaning anything.

Measured against Dart AOT: ParparVM is 1.81x faster by geometric mean, with
hashMapChurn 7.08x, stringBuilding 4.58x, intArithmetic 2.74x and valueEscape
2.50x; parity on longArithmetic; Dart ahead only on objectAllocation (0.83x).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flutter's paintImage ends with `fit ??= BoxFit.scaleDown`, so a widget that
names no fit draws its artwork at its own size when the box is bigger, centred
in the slack. This runtime read that default as `contain` and BoxFit.scaleDown
itself fell through to the `contain` arm, so every under-sized picture was blown
up to fill its box with no way to ask for the real behaviour.

Image.asset also accepted cacheWidth and cacheHeight and dropped them on the
floor. They bound the decoded bitmap, so they bound everything downstream: the
intrinsic size layout constrains and the size the picture is painted at. They
apply in decoded pixels, before the density variant is rescaled to the screen,
and they never enlarge -- ResizeImage passes allowUpscaling false. dart:ui
decodes to exactly both dimensions when both are given, so the aspect ratio
survives only while one of them is left open.

Layout now constrains the way RenderImage does, preserving the aspect ratio
rather than clamping each axis on its own; clamping independently gave a box
with the constraint's width and the picture's own height, which sat the artwork
in vertical slack and pushed everything below it down by half the difference.

Measured against the reference gallery, worst-first over 47 routes: the mean
share of the screen that is wrong falls from 4.64% to 4.45%. The lead photo
route goes 19.11% -> 9.50%, and the reply study's attachment thumbnails, which
were drawn at 432px against the reference's 200px, now match it exactly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codename One asks a box how wide it would like to be before it lays that box
out at the size it will occupy. That question arrives as a real layout call --
not a dry one, so the existing guard did not catch it -- with the width
unbounded, and the answer to it is discarded a moment later when the parent
comes back with the box it actually chose.

A Flutter builder is allowed to LATCH. The 2D-transformations demo centres its
board against the first constraints.maxWidth it is ever shown and keeps that
matrix for the life of the route, so the speculative call decided the screen: it
was handed an infinite viewport, computed a centring offset from it, and the
board drew in the corner with its left edge cut off. Flutter never reaches the
builder that way -- LayoutBuilder refuses intrinsic queries outright rather than
running the callback against a box it will not be painted at.

So sit out one unbounded pass. If the next one is unbounded too then this really
is an unbounded layout -- a viewport's child -- and the builder runs against it
as Flutter would. Sitting out has to drop this element's cached layout result or
the pass that follows is served the placeholder instead of running the builder,
which turns "sit out once" into "never build" for exactly those viewport
children; the dry path already does the same thing for the same reason.

Worst-first over 47 routes, the mean share of the screen that is wrong falls
from 4.45% to 4.18%. The transformations demo, previously the worst route, goes
23.38% -> 14.72%, the reply study 19.98% -> 15.65%, and no route regresses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both were accepted and dropped. Without labelPadding the labels have nothing
between them, so Crane's three tabs -- Fly, Sleep, Eat -- rendered as the single
word FLYSLEEPEAT; a bar that names none now gets Flutter's kTabLabelPadding of
16 logical pixels either side rather than zero. And labelColor and labelStyle
were stored and never reached the labels, so a bar whose labels are white on a
coloured header painted them in the default ink instead. Flutter styles the
selected tab with labelStyle/labelColor and the rest with
unselectedLabelStyle/unselectedLabelColor, through the surrounding text style,
which is what DefaultTextStyle already does here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flutter resolves it as `AppBar.titleTextStyle ?? AppBarTheme.titleTextStyle ??
textTheme.titleLarge`. The last link was missing, so a bar whose theme names no
title style -- which is most of them -- fell through to whatever size a bare
Text picks. That is about 16 logical pixels against titleLarge's 22, and every
title in the gallery rendered at roughly seven tenths of its size: the
typography demo's title measured 43 device pixels tall where the reference is
56, and 241 wide where the reference is 335.

Measured against the reference it now matches exactly -- 56 tall, and 323 wide
against 335. The aggregate diff does not move: a title drawn at its real size
covers more of the screen in glyph edges, and our rasteriser and the reference's
disagree along every one of them, so nine routes drift by three to six
hundredths of a percent while the typography demo, whose title this most
obviously fixes, does not change at all. The absolute measurement is the check
that matters here, not the aggregate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four defects, all in the same button.

floatingActionButtonLocation was accepted and discarded, so every FAB floated
at the bottom right whatever it asked for. Reply's compose button is
centreDocked: it belongs centred, with its centre ON the bottom bar's top edge,
which is what lets a notched bar cut a hole for it. Its centre now lands at 562
device pixels, which is where the reference puts it.

The FAB was also mounted third of six, and components attach in mount order,
which is this host's paint order -- so the bottom bar and the persistent footer
were painted over it. Flutter's _ScaffoldSlot orders the FAB after both. A
docked FAB lost its whole bottom half that way.

Its shape came from whatever the Codename One theme in force carried for the
UIID, which for this runtime's theme is nothing, so a background colour filled a
hard-cornered block. A Material 3 FAB is a rounded square of 16 logical pixels,
not the disc Material 2 used.

And its colours were never resolved at all: Material 3 defaults them to
primaryContainer over onPrimaryContainer. The bottom-app-bar demo drew a pale
lavender button with a purple glyph where the reference is purple with a white
one -- the two roles exactly inverted.

Separately, Material understood RoundedRectangleBorder and nothing else, so a
CircleBorder fell through to a corner radius of zero. Reply's compose button is
not a FloatingActionButton on mobile at all: it is an OpenContainer whose
closedShape is a CircleBorder, and it drew as an orange square. A CircleBorder
is the circle inscribed in the box, so as a rounded rectangle it is a radius of
half the shorter side; the button now measures 168x168 filling 0.743 of its box
against the reference's 167x168 at 0.745.

Worst-first over 47 routes the mean falls from 4.17% to 4.01%. Every study
improves by about one and a third points -- their Back buttons are extended FABs
that were wearing the wrong colour -- and nav_rail moves the other way by 0.13
because its FAB is now correctly purple while the navigation rail it belongs to
is still missing entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
InputDecoration.prefixIcon was stored and never read, so Crane's search form --
four rows whose whole affordance is the glyph saying what the row is for --
rendered as four bare capsules. Codename One's text field has no icon slot, so
the icon and the editor now share a container and the decoration's surface moves
onto it, because in Flutter the fill and the border enclose the icon too. Every
read and write of the editor goes through a held reference rather than through
component(), which is no longer the editor when there is an icon.

Two units traps on the way in. FontImage sizes glyphs in MILLIMETRES, so passing
24 logical pixels asked for a 24mm glyph and drew a person icon taller than the
row it sat in; Dp.mm is the conversion, the same one IconRenderElement uses. And
the glyph's colour falls back to the ambient IconTheme when the Icon names none,
without which it is painted in the default ink -- black, on Crane's purple rows.
Measured against the reference the icon now lands within a few pixels of it.

TextField.style was also stored and never read, so a field rendered at whatever
size Codename One's default font happens to be.

Crane moves 17.22% -> 17.33%. The icons are right and were absent before; what
the extra tenth measures is the hint beside them, which is still half again too
tall -- 56 device pixels of ink against the reference's 36 -- so drawing the icon
correctly shifts a still-wrong placeholder into a new wrong position. Applying
Flutter's hint chain on top of this was tried and reverted: it changed the
colour and not the size, because Codename One's hint label does not take the
derived font, and that measured worse still at 17.80%. The size is the thing to
fix first and it is not a styling problem.

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

Flutter resolves a decoration through InputDecoration.applyDefaults: every
field the widget leaves unset falls back to the ambient inputDecorationTheme.
That theme was held opaquely on ThemeData and never read, so a study that names
its field styling once on the theme rather than on each field got none of it.

Rally is the case that shows what it costs. Its login fields carry nothing but a
labelText; the dark fill they sit in is named on the theme. Without the fallback
they rendered as two white blocks on a dark page -- the largest wrong area on
the route by a wide margin. The route goes from 11.30% of the screen wrong to
6.26%, and the mean over 47 routes from 4.01% to 3.90%. Shrine, which names
content padding the same way, moves with it.

`filled` is a primitive on both sides, so "unset" and "false" cannot be told
apart; a theme that asks for a fill therefore wins over a decoration that simply
did not mention one, which is the case the studies exercise. The borders stay
opaque: most of them are shapes this port cannot draw, and none of the gallery's
themes depend on one to be legible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flutter does not clip a CustomPainter to the box it was handed. An ancestor
ClipRect does that, so a painter is free to draw well outside its own size and
routinely does. Codename One clips every component to its bounds -- Component's
internalPaintImpl does it unconditionally -- and those bounds are read in the
coordinates the graphics is currently painting in. When an ancestor Transform
has shifted the origin they move with it, so the drawing is clipped to where the
box WOULD be rather than to where it is, and whatever the shift brought into
view is cut away instead.

The 2D-transformations demo centres a board wider than the screen by translating
it 64 logical pixels left. The board was clipped to the surface's own width
first and then shifted, so the rightmost 64 logical pixels of the viewport were
bare background: a dark strip down the right-hand side exactly as wide as the
shift. Hexagons stopped at device x 884 where the reference carries them to
1072.

The shift is recoverable rather than guessed. A graphics being painted through
has accumulated exactly its ancestors' offsets, so absent a transform its
translation plus the component's parent-relative position is its absolute
position; whatever that identity is out by is the transform. Clip to the box the
component occupies on screen.

The route goes from 14.76% of the screen wrong to 10.02%, and the mean over 47
routes from 3.90% to 3.80%, with nothing else moving.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sitting out an unbounded pass was too broad a rule, and it emptied the reply
study's entire mail list.

Unbounded on its own is not the signal. A viewport's child is legitimately
unbounded along the scroll axis and Flutter hands it infinity too: a mail card
in a vertical list gets a TIGHT width of 367 and a height of zero to infinity,
and its builder is meant to run against exactly that. Sitting it out returned a
zero height, which the list then kept, so every card in the study collapsed and
the route rendered as bare background.

What separates a real pass from a measurement is the CROSS axis. A viewport
gives its child a tight cross-axis extent; "how big would you like to be" is
loose in both directions, which is what the transformations demo was answering
when it latched an infinite viewport.

The diff score did not catch this. It went DOWN, 18.96% wrong to 15.65%, because
blank background differs from the reference less than mis-rendered cards do --
the metric rewards deleting content. It is caught now by a test that pins the
viewport-child case directly, and that test fails against the old rule.

Restoring the cards puts the route back at its real 20.22% and the mean over 47
routes at 3.93%, up from a 3.80% that was partly measuring an empty screen.

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

Three things were wrong about its geometry, all found by measuring one against
the reference rather than by reading the code.

Its SIZE came from the component's preferred size, which is the glyph plus
whatever padding the theme in force carries. With no theme entry for the UIID
that is 83 device pixels against the reference's 168 -- less than half. Material
fixes it: a regular button is a 56 logical pixel square, and an extended one
fixes its height too and lets only the width follow its label. That height is 56
in Material 3, not the 48 Material 2 used, which is what the reference draws.

It also could not use Codename One's own FloatingActionButton at all. That class
re-installs its own circular border from styleChanged() every time the
background colour is set, so the Material 3 shape put on it was replaced the
moment the colour followed and the button painted 96 device pixels of surface
inside its 168 pixel box. The extended form already had to be a plain Button
wearing the same UIID, for its own reason; both forms are now.

And it sat too LOW. Flutter measures the button from the bottom of the CONTENT,
which excludes the display's own bottom padding, so measuring from the bottom of
the scaffold put it over the home indicator instead of above it -- 102 device
pixels out on the starter study.

The starter study's button now lands at exactly 168x168 at (909, 2118), which is
where the reference puts it. Eight routes improve, the motion demo by 1.15
points and the starter study by 0.98, and the mean over 47 routes goes from
3.84% to 3.78%. The navigation-rail demo moves 0.34 the other way: its button is
now correctly sized and placed in a screen whose rail is still missing entirely,
so there is more of it to be wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It built `leading` and returned it. Everything that makes a rail a rail -- the
destinations, their icons and labels, the selection, the surface it all sits on
-- was captured and dropped, so the navigation-rail demo drew its create button
floating in the middle of an otherwise empty page.

The rail is now a fixed-width column: the leading widget between Flutter's
vertical spacers, then a slot per destination, then the trailing widget. A
destination shows its selectedIcon when it is the selected one, and its label
when labelType asks for it -- all of them, only the selected one, or none.

Two defaults were also wrong by being absent. useIndicator is a primitive that
read false, so the selected destination had no pill behind its icon; Material 3
draws one, tinted with secondaryContainer. And a rail that names no
backgroundColor falls back to colorScheme.surface, without which the demo's rail
was invisible -- white destinations on a white page, which is why the whole
control could be missing and the diff barely moved.

Separately, the FAB's glyph was rasterised BEFORE its style was applied, so the
theme's default ink was burned into the image and the button wore a dark plus
sign on a purple surface where the reference has a white one. The glyph now
follows the style, in both the create and update paths.

The demo goes from 2.68% of the screen wrong to 1.68%, the starter study from
2.25% to 1.95%, and six other routes follow the glyph fix. The mean over 47
routes goes from 3.78% to 3.73%, with nothing regressing.

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

check-copyright-headers runs on every pull request to master and validates
ADDED AND MODIFIED sources, so editing a file that has no header fails it just
as surely as adding one. Every file this branch's parity work touched was in
that state, which would have turned the gate red on a change that has nothing
to do with licensing.

This covers only the twenty-six files that work touched. The condition is
branch-wide and much larger -- 763 of flutter-runtime's 772 sources and all 38
of dart-transpiler's carry no header -- and sweeping those is a separate change
that would swamp anything it travelled with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… clears it once

The bar was Material 2's 56 logical pixels. Material 3 makes it 80, which is
what the reference draws -- exactly 80 in the bottom-app-bar demo, measured at
the pixel. Where the reply study's bar looks 114 tall it is that same 80 over 34
of the scaffold's own dark background showing through the display's bottom
padding; the padding is not the bar's to carry, and giving it to the bar made
every embedded bar 34 logical pixels too tall.

The floating action button was also clearing the bottom twice. It measures from
the bottom of the CONTENT, and a bottom bar IS what holds the content off the
edge, so adding the display's padding on top of the bar's height double-counted
it. On the reply study that lifted the docked button clear of its own bar and
into the mail list, where a card painted over it and it disappeared entirely --
a button that had been visible a commit earlier. The inset now applies only when
there is no bar, which is the case the starter study exercises and where the
button lands on the reference's pixel.

The reply study goes from 19.89% of the screen wrong to 17.50%, the
bottom-app-bar demo from 5.49% to 3.80%, and the mean over 47 routes from 3.73%
to 3.65%, with nothing regressing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…one inside it

InputDecoration has two icon slots and they are not the same place. prefixIcon
sits INSIDE the decoration, enclosed by its fill and its border; `icon` sits
outside it, to the left, with Material's gap between. Only the first was drawn,
so the text-field demo -- whose person, phone and envelope are all the outside
kind -- had none of them.

They differ in where the surface goes, which is the whole of the change: an
inside icon moves the fill onto the row it shares with the editor, an outside
one leaves the editor its own and stands clear.

The demo's score does not move: the glyphs are small, and what dominates that
route is the field styling around them -- an outline where the reference draws
an underline, and a placeholder half again too tall. The icons were absent and
are now in the reference's place, at the reference's size.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The hint chain was written once and reverted, because applying it changed the
colour and not the size and measured worse for it. The reason was a bad probe:
it derived from whatever font the theme had left on the component, and a SYSTEM
font does not derive -- Font.derive returns it unchanged -- so every size handed
to a placeholder was dropped on the floor. TextRenderElement does not hit this
because it resolves the style's named family to a bundled TrueType face first,
and those do derive.

So the field does that too, and with the size actually landing, Flutter's chain
is right after all: titleMedium merged with the field's own style, recoloured
with the theme's hintColor, then merged with an explicit hintStyle. The recolour
matters on its own -- a placeholder that keeps the INPUT's colour is white on
every row of Crane's search form, where the input colour is white.

Crane's placeholder now measures 36 device pixels of ink against the reference's
36. It was 56. Crane 17.15% -> 16.75%, shrine 3.02% -> 2.74%, and the mean over
47 routes 3.65% -> 3.63%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A bar flush with the screen leaves nothing under it, and the reference draws
something there. The reply study's bar reads as one 114 logical pixel block of
colour with its Inbox row in the top 56; laying Material's 80 flush instead put
a white strip where the reference is dark and pushed the whole body 34 lower --
the single largest wrong band on the route, 280 device pixels tall and 97%
wrong.

So the strip is laid out that much taller, stays flush, and holds its content at
the top. The body stops short of the band with it.

Only when the scaffold IS the display. That is the one thing separating a
full-screen study from a demo shown inside a card, and it is why keying on root
mode failed earlier: both are embedded in the gallery's page. The size says it,
and by the time the strip is positioned the size is known -- which is why this
belongs in layout and not in the widget, where an earlier attempt had to guess
before anything had been measured.

Measured against the reference the band is now 114.0 logical pixels against
114.0, from 2094 to the bottom edge in both. The reply study goes 17.50% ->
13.87%, the bottom-app-bar demo is untouched at 3.81%, and the mean over 47
routes 3.63% -> 3.55%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tter's does

The gallery's HOME screen had never been measured. The reference set covers 47
demo and study routes and the home screen is not one of them, so no golden
existed, the sweep never opened it, and the first thing anyone sees was the one
screen nobody was checking. Measured against a golden generated for it, it came
in at 20.10% wrong -- worse than any route in the set.

Almost all of it was one thing. Flutter's BoxScrollView does not leave a null
padding null: it takes the ambient MediaQuery padding along its OWN axis,
applies it, and removes it for everything inside so it is not counted twice.
That is how a full-screen list keeps its first item out from under the display
cutout without anyone writing a SafeArea, and the home list relies on it
entirely -- it has no padding, no SafeArea and no app bar. Without it the title
sat 133 device pixels too high, under the island, and every following thing with
it. It now lands at rows 215..271 against the reference's 216..272.

The Scaffold's half of the same rule was also missing: the body loses its top
padding when an app bar stands in for it, and its BOTTOM padding when a bottom
bar or a footer does. Leaving the bottom in place under a bottom bar counts it
twice.

The home screen goes 20.10% -> 3.10%. Three routes move the other way by 1.9
between them, and the 48-route mean lands at 3.59.

The home golden is generated with isTestMode OFF, unlike the demo routes. There
it does not only suppress the coach mark: the home page reads it as
`initiallyExpanded: ... || isTestMode`, so a reference captured with it on shows
the Material category already open, which is not what the app does when someone
launches it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gallery's carousel cards were square on device and round in every sweep, and
the sweep is why: the two are not the same code path. Only the iOS port
implements isRoundedImageDrawSupported, so a rounded picture is a rounded COPY
of the bitmap on the desktop and a drawImageRounded call on iOS.

That call rounds what is DRAWN. A cover fit paints a rectangle larger than the
component, so its rounded corners land outside the clip and what shows is four
hard ones. Measured on the simulator: the reference's corner walks in 188, 176,
169, 163, 160, 159 over the first 35 rows and ours sat flat at 159 the whole
way.

An overflowing fit therefore goes through the copy, which scales to the box
first and rounds that. Nothing on the desktop moves -- it was already taking
that path for every rounded image -- so this is verified on the device, not in
the sweep.

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

stripChrome zeroes the form's padding and margin because Flutter owns the whole
canvas -- the widget tree draws its own safe areas. It missed the safe-area
FLAG, which is a layout inset rather than padding, so zeroing the style never
touched it.

With it left on the canvas is inset twice, and a band of the FORM's own colour
is left above everything the app drew: a white strip across the top of the
gallery on iOS, where the reference has the page carrying on behind the status
bar. Measured on the simulator, our top rows were near-white across the entire
width where Flutter's are the page's own colour.

It costs nothing on a port with no display cutout, which is why it survived
every desktop sweep -- the 48-route mean is unchanged at 3.59%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fast path that gives an EncodedImage its size without decoding read
the width and height at fixed offsets 16 and 20, on the stated grounds
that "IHDR is always the first chunk". That is true of a conforming PNG
and false of the ones an iOS app ships: Xcode rewrites every bundled PNG
into Apple's CgBI form, which prepends a four-byte CgBI chunk. We were
reading that chunk's payload as the width and its CRC as the height, so
every bundled picture on device was measured to a garbage aspect -- the
gallery's category icons drew 139x84 where the reference draws 139x154.

Nothing on the desktop can see this. The desktop port reads the same
asset straight from the jar, where it is still an ordinary PNG, so the
sweep was pixel-exact on the very screens that were visibly wrong on the
device.

Walk the chunk list to IHDR instead. Anything without one -- a truncated
file, or a container we do not know -- answers {-1, -1} and falls back to
the decoding path, which is slower but never wrong.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39e3f69fb3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread maven/dart-runtime/src/main/java/dart/core/DartUri.java Outdated
Comment thread maven/flutter-runtime/src/main/java/com/codename1/flutter/material/Dialogs.java Outdated
Comment thread maven/dart-runtime/src/main/java/dart/core/DartLongMap.java
Comment thread maven/dart-runtime/src/main/java/dart/runtime/DartRuntime.java Outdated
shai-almog and others added 3 commits September 24, 2026 12:35
…odifiable lists; Dart concurrent-modification errors

A thrown non-exception (a string, an app token) now travels in DartThrown
and reaches catch clauses and future error handlers as the same object.
List.unmodifiable and Uri.pathSegments refuse every write, including on
the primitive lists, and map, set and int-map iteration report changes
as ConcurrentModificationError after each callback. Adds identical().

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tart/end, animateToPage timing, forward(from:), maxLength in characters

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…st.unmodifiable; coerce int-map keys

Catch clauses become a single catch (Throwable) testing the Dart clauses
in order: on Error / on Exception are tests, a thrown value is matched by
its own type and bound as itself, and a filtered clause no longer skips
the ones after it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e375507c2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread maven/flutter-runtime/src/main/java/com/codename1/flutter/provider/Selector.java Outdated
Comment thread maven/dart-runtime/src/main/java/dart/runtime/DartRuntime.java Outdated
Comment thread maven/dart-runtime/src/main/java/dart/core/DartList.java
Comment thread maven/flutter-runtime/src/main/java/com/codename1/flutter/ObjectKey.java Outdated
Comment thread maven/dart-runtime/src/main/java/dart/typed_data/Uint8List.java Outdated
Comment thread maven/dart-runtime/src/main/java/dart/typed_data/ByteData.java
Comment thread maven/dart-runtime/src/main/java/dart/core/DString.java Outdated
shai-almog and others added 4 commits September 24, 2026 13:04
…om the start-up gate

The Android emulator's speed swings up to 2x between runs: in one run it
booted in 75s instead of ~57s and Flutter's own cold start was 2244 ms
against 1031-1505 ms, and Codename One's rose with it; Linux and Windows,
running the same code, were unchanged. When the pinned Flutter build is
slower than its recorded reference, Codename One's start-up is judged
after the same ratio. One-sided: a normal runner is gated as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nt hashes, Dart whitespace in int.parse, overflow-safe ByteData offsets

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… script, ObjectKey identity for boxed numbers

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…bind a Dart ConcurrentModificationError

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c022e458a2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread maven/dart-runtime/src/main/java/dart/runtime/DartRuntime.java Outdated
Comment thread maven/dart-runtime/src/main/java/dart/async/Future.java
Comment thread maven/flutter-runtime/src/main/java/com/codename1/flutter/widgets/Image.java Outdated
shai-almog and others added 3 commits September 24, 2026 13:51
…rowing iterable, replaceFirst with a start, awaitAny

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…he page when no replacement resolves, NetworkImage scale and content-keyed headers

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…wait of a non-Future

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20bf6fa52f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread maven/dart-runtime/src/main/java/dart/core/DartIdentityMap.java Outdated
Comment thread maven/dart-runtime/src/main/java/dart/core/DartMap.java Outdated
Comment thread maven/flutter-runtime/src/main/java/com/codename1/flutter/MediaQueryData.java Outdated
Comment thread maven/flutter-runtime/src/main/java/com/codename1/flutter/intl/DateFormat.java Outdated
shai-almog and others added 7 commits September 24, 2026 14:23
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… same-size structural changes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…es the safe area, UTC dates format in UTC, notifiers own their listener lists

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s its contributor line

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The review-round correctness fixes since f5caddd (Dart error mapping,
identity collections, ConcurrentModificationError checks, late fields,
Characters, Future.wait semantics, and more) added ~8.6k lines to the
runtimes and transpiler. Executable code grew on every platform by
1.2-2.3%; JavaScript crossed its 2% tolerance at +2.25%. Recorded from
the gate's own candidate in run 35993126329.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ilation

DialogInWindowTest: master's #5888 carries the same wasTouchDevice restore; take its copy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@shai-almog

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 424c67cafa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/flutter-bench/platforms.py Outdated
Comment thread scripts/flutter-bench/publish_benchmark.py Outdated
Comment thread scripts/flutter-bench/benchlib.py Outdated
shai-almog and others added 4 commits September 24, 2026 21:35
…, publish only complete runs

- _time_stream counted SETTLE_S from process launch, so a side that
  reached its first frame late had little or no idle time before memory
  was sampled. The idle period now starts at the marker, and
  LAUNCH_TIMEOUT_S bounds only the wait for it.
- check_regressions skipped a gated baseline metric the run did not
  produce, so a desktop build that never reached its first frame still
  passed on its size rows. That is now a regression finding.
- publish_benchmark refuses a document missing any platform: a leg that
  dies before writing its result would otherwise drop that platform from
  the public page. benchlib.PLATFORM_IDS is the list, held to the
  workflow's plan matrix by a test.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
hasEffect took calls on a value, static calls, constructor calls and
getters to be pure, so Pair(b: counter.next(), a: counter.next()) ran the
calls in parameter order and swapped the results. Any call, non-const
constructor, getter on a value and index operator is now an effect, and
a variable read nested in an expression is ordered too. Measured on the
gallery: 44 -> 190 sequenced call sites, +0.44% compiled class bytes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ilation

scripts/ci/retry.sh: master's copy is a superset of this branch's (the same
negative-cache purge, looked for in more places, plus DNS failures).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… on any size growth

The benchmark is a gate, not a scoreboard. check_behind fails any
measured metric whose ratio is under 1.00 -- Flutter is measured
interleaved on the same runner, so this needs no baseline and no
tolerance. Size metrics get zero tolerance: they are deterministic for a
given tree and toolchain, so any growth fails and a deliberate increase
is re-baselined by committing the run's candidate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

2 participants