Repository navigation
perry/ui-ios: helper-built widgets clobbered when called inside a for-loop and pushed to HStack/VStack #634
Description
Summary
When a helper function that builds a perry/ui widget is called repeatedly inside a for / for…of loop and the result is pushed into an array passed to HStack / VStack, the resulting children are not rendered (zero-size / invisible). The same widget tree built via literal sibling calls in the array argument renders correctly.
Tested on perry 0.5.727, --target ios-simulator (iPhone 16 Pro running iOS 26 sim).
Minimal repro
import {
App, VStack, HStack, Text, Button,
setPadding, setCornerRadius,
widgetSetBackgroundColor,
textSetColor, textSetFontSize,
} from "perry/ui";
function chip(label: string): Widget {
const b = Button(label, () => {});
textSetColor(b, 0.8, 0.07, 0.26, 1.0);
textSetFontSize(b, 14);
const wrap = VStack(0, [b]);
widgetSetBackgroundColor(wrap, 1.0, 1.0, 1.0, 1.0);
setCornerRadius(wrap, 5);
setPadding(wrap, 8, 14, 8, 14);
return wrap;
}
// ── Bug path: build via a for-loop that pushes to an array ──────────
const labels = ["A", "B", "C"];
const looped: Widget[] = [];
for (const label of labels) {
looped.push(chip(label));
}
const loopedRow = HStack(8, looped);
// ── Working path: literal sibling calls inside HStack ──────────────
const literalRow = HStack(8, [chip("A"), chip("B"), chip("C")]);
const heading1 = Text("Loop-built (broken):");
textSetFontSize(heading1, 16);
const heading2 = Text("Literal-sibling calls (works):");
textSetFontSize(heading2, 16);
const body = VStack(16, [heading1, loopedRow, heading2, literalRow]);
setPadding(body, 24, 16, 16, 16);
widgetSetBackgroundColor(body, 0.94, 0.94, 0.96, 1.0);
App({
title: "Loop bug repro",
width: 390, height: 300,
body,
});Compile and run:
mkdir -p dist
perry compile src/main.ts --target ios-simulator -o dist/repro
xcrun simctl install booted dist/repro.app
xcrun simctl launch booted com.perry.reproExpected vs actual
Expected: two rows of three chips (A B C), top row labeled "Loop-built", bottom row labeled "Literal-sibling".
Actual: the loop-built row renders nothing at all — large empty space below the heading. The literal-sibling row renders correctly. Same chip() helper, same number of children, same parent HStack — only difference is loop-pushed vs literal array.
Workaround
Unroll the loop into a literal array of helper calls. Verified working in sharity-perry-app's Filter screen — 5 radius chips and 10 category chips rendered correctly only after replacing for (const r of RADII) row.push(chip(r)) with [chip("5 km", …), chip("10 km", …), …].
Notes
- TypeScript type-checks both forms; the bug is purely a runtime / codegen concern.
- Does not depend on the helper's specific widget shape — the symptom appeared in this codebase first with chip-shaped Buttons-in-VStacks; the minimal repro above uses the same shape but the issue is likely about how the codegen handles the iterated widget-handle creation when a helper produces multiple handles per call.
- I haven't tested this on other targets (macos, android, web). If it's iOS-specific, the bug likely lives in
perry-ui-ios's widget-handle bookkeeping rather than the codegen itself.
Activity
fix(ui,transform,codegen): closes #612 #634 #635 — NavStack state-dri…