Releases: PerryTS/perry
v0.5.294
Hotfix for v0.5.293's failed publish. v0.5.293 tagged successfully but release-packages.yml's await-tests gate failed: 19 macOS UI doc-tests + iOS simctl build hit an _js_stdlib_process_pending undefined-symbol link error. v0.5.293's GH release page exists but ships no binaries. v0.5.294 fixes the underlying bug.
Fix
fix(ui)— UI crates (perry-ui-{macos,ios,tvos,visionos,gtk4}, 18 files) now calljs_run_stdlib_pumpinstead of hard-linkingjs_stdlib_process_pending. The trampoline atcrates/perry-runtime/src/lib.rs:121is unconditionally exported and dispatches via the registered-callback pattern (js_stdlib_has_active_handlesalready worked this way atlib.rs:144).chore(parity)— re-addedtest_gap_console_methodstotest-parity/known_failures.jsonasci-env; v0.5.290's drop was premature. Investigatenormalize_outputbroadening separately.
Root cause
Cargo feature unification. perry-stdlib/Cargo.toml:81 says perry-runtime = { features = ["stdlib"] }. When perry's auto-optimize compiles both crates in one cargo invocation (crates/perry/src/commands/compile.rs:1844), the stdlib feature gets activated on perry-runtime — which triggers the #[cfg(not(feature = "stdlib"))] gate at crates/perry-runtime/src/lib.rs:65 and excludes mod stdlib_stubs;, removing _js_stdlib_process_pending from libperry_runtime.a. perry's compile then enters runtime-only link mode (no libperry_stdlib.a in the link line) and the symbol is undefined.
Local single-package builds (cargo build -p perry-runtime) don't unify features, so the stub is included and the bug stays hidden in dev. feedback_recompile_ui_lib.md was a workaround for this latent bug — rebuilding the macOS UI lib happened to align local artifacts with locally-built libperry_runtime.a (which had the symbol).
Verified locally by reproducing the CI auto-optimize path:
./target/release/perry compile docs/examples/ui/animation/fade_in.ts -o /tmp/fade_in_test
…
Linking (runtime-only)...
Linking perry/ui (native UI) from target/release/libperry_ui_macos.a
Wrote executable: /tmp/fade_in_test
What v0.5.293 was supposed to ship
If you want the full changelog from v0.5.178 through v0.5.293 (115 commits — generational GC default ON, SSO default ON, JSON tape-parse + lazy default ON, Windows lightweight toolchain, visionOS support, end-to-end notification work on iOS + Android, etc.), see https://github.com/PerryTS/perry/releases/tag/v0.5.293. The notes there are accurate; only the binaries failed to publish.
v0.5.293
First tagged release since v0.5.178 — 115 commits of accumulated work. Headline changes: generational mark-sweep GC and SSO are now default ON, JSON parsing went tape-based + lazy by default, Windows toolchain dropped its Visual Studio dependency, and Native UI grew end-to-end notification support on iOS + Android.
Highlights
- Generational mark-sweep GC default ON — Phases A-D land the shadow stack, write barriers, nursery/old-gen split, non-moving tenuring, copying evacuation pass, and per-cycle idle-block return-to-OS.
PERRY_GEN_GC=0reverts (bisection only). (v0.5.217 → v0.5.239) - SSO (Small String Optimization) default ON — runtime infrastructure + codegen three-way PropertyGet branch + consumer arms behind
PERRY_SSO_FORCEwhile baking, then default-flipped at v0.5.216. (v0.5.213 → v0.5.216) - JSON parsing rebuilt — schema-directed
JSON.parse<T>(blob), tape-based parse foundation, lazy parse + lazy stringify (default ON), per-element sparse materialization, walk-cursor + adaptive materialize threshold. (v0.5.200 → v0.5.210) - Windows lightweight toolchain — winget LLVM + xwin, no Visual Studio needed (#176, v0.5.199); plus actionable clang-missing / link.exe error messages and
perry doctorLLVM checks. - visionOS simulator support —
--target visionos-simulator(v0.5.185).
Performance
- JSON: lazy parse default ON (v0.5.210), per-element sparse materialization (v0.5.208), NEON/SSE2 string scanner (v0.5.197).
- GC: arena block size 8 MB → 1 MB (v0.5.196), trigger threshold 128 MB → 64 MB (v0.5.198), idle nursery blocks returned to OS (v0.5.235).
- Buffer.alloc fast-path via per-thread bump slab (#92, v0.5.190); BigInt arena alloc +
BigInt(str) ===fix (#92, v0.5.187);parseFloatzero-alloc +Infinityfix (#92).
Runtime / Codegen Fixes
- JSON:
JSON.stringifyof plain f64 segfaulted (v0.5.286); restore#[no_mangle]onjs_json_stringify(v0.5.211). - NaN:
NaN==NaN+ ECMAScript number formatting (v0.5.281);NaN/InfinityToInt32 in(x) | 0(v0.5.280); SSO + property-read NaN bug (v0.5.279). - Promises: microtask FIFO + thrown-handler propagation (v0.5.284);
Promise.all/race/anynon-promise discrimination (v0.5.263);Promise.allSettlednon-Promise values;queueMicrotasknever running +performance.now()always 0 (#156, v0.5.180). - Codegen: cross-module class getters/setters returned undefined (twice — c473934, 2a08855); inline pass remaps closure captures (v0.5.273); don't inline functions with rest params (v0.5.278); i32 loop counter for number-typed bounds (#168, v0.5.188);
js_try_endonreturninside try body; FFI manifest consumption for native-library ABI; for-loopcontinueskipping update on--target web(#137). fs.readFileSync(path)returns Buffer when no encoding (v0.5.277); BigIntfromTwos/toTwostwo's complement (v0.5.255);console.timeresolution (#155, v0.5.181);Int32Arraylength=0 +Uint8ClampedArrayno-clamp + negative NaN (#157, v0.5.184);isWellFormed/toWellFormedlone-surrogate detection (#29).- WASM: drop excess args at WASM call sites (#183, v0.5.205).
- Type predicates + lazy audit (v0.5.212); HIR silent fall-throughs in lowerer + monomorph (v0.5.249).
Stdlib & Node Compat (#187 follow-ups)
- Redis (ioredis) end-to-end + dispatch-table-symbol-mismatch fix (v0.5.270); pg + mongo async-factory pattern (v0.5.275); AsyncLocalStorage end-to-end (v0.5.261); decimal.js (v0.5.259); commander runtime + codegen
.action()invocation (v0.5.250);new T()bug on EE/LRU/WSS (v0.5.252); Buffer numeric reads intrinsified (#92, v0.5.183). - Fastify end-to-end integration test + two dispatch fixes (#174, v0.5.189); mysql2 + pg
connection.execute()param binding (#143, v0.5.182).
Native UI
- Apple (iOS / macOS):
notificationSend(#94, ui-ios v0.5.193), client-side remote-push token + receive (#95, v0.5.243), scheduled + cancellable local notifications (#96, v0.5.244), notification tap callback (#97, v0.5.254). - Android:
notificationSend(#94, v0.5.256), schedule + cancel notifications (#96, v0.5.260), notification tap callback (#97, v0.5.258), FCM register + receive (#95, v0.5.262).
Platform / Toolchain
- Windows: lightweight toolchain (#176, v0.5.199);
find_lld_link/find_perry_windows_sdkcfg gate hotfix (v0.5.201);/SUBSYSTEM:CONSOLEfor non-UI PE builds (#120, v0.5.179); actionable clang/link.exe error messages (#176 follow-ups, v0.5.191/.192). - npm:
libc:glibcfield onlinux-x64andlinux-arm64packages (#116/#161).
Documentation & Benchmarks
- Polyglot benchmark suite expansions: Kotlin + JSON polyglot (v0.5.241/.242), simdjson + AssemblyScript+json-as peers (v0.5.274),
loop_data_dependent+bench_field_accesslanded (v0.5.272). - Methodology hardening: RUNS=11 median + p95 + σ + macOS pinning (v0.5.248); 04_array_read 211 MB peak RSS explained (v0.5.276); FP-contract caveat (FMA-contract vs no-contract clustering) on
bench_loop_data_dependent(v0.5.293). - Node TS-strip leveling:
json_polyglot/run.shprecompiles Node TS to .mjs as untimed setup so Node isn't charged for--experimental-strip-typesruntime parse (v0.5.293). - GC academic + industry lineage appendix; Phase D roadmap closeout (v0.5.239/.240).
- CLAUDE.md condense — 124 verbose Recent Changes entries (~242 KB) migrated to CHANGELOG.md, file went 254 KB → 12 KB (v0.5.292).
Repo & CI Hygiene
chore(repo)v0.5.293: untrack 465 Android Gradle cache files, gitignoreandroid-build/.gradle//build//app/build/,docs/examples/_reports/, external demoassets/, and stray repro binaries.- CI macos-14 jobs no longer OOM on disk space (v0.5.289-.291).
- Memory stability suite for RSS-leak + GC-aggression regressions (v0.5.233).
- Stub-audit cleanups:
test_json,test_gap_console_methods,test_gap_class_advancedremoved fromknown_failures.json.
Escape Hatches
PERRY_GEN_GC=0— revert generational GC to full mark-sweep.PERRY_SHADOW_STACK=0— disable shadow-stack precise roots.PERRY_GEN_GC_EVACUATE=1— opt into copying evacuation (default OFF, work-saving on workloads where nothing tenures).PERRY_WRITE_BARRIERS=1— opt into codegen-emitted write barriers (runtime barrier always exists).PERRY_GC_DIAG=1— per-cycle GC diagnostics.
v0.5.178
Highlights
Fix-forward release that unblocks the stuck v0.5.177 release-packages pipeline. Three distinct CI regressions fixed — UI doc-tests now link on all three host OSes, cargo-test no longer SIGABRTs on the HIR repro suite, and new Set([...]) without explicit type args stops returning undefined on .has().
Fixes
-
App({...})doc-tests on macOS/Ubuntu/Windows: everydocs/examples/ui/*.tsexample started failing with "App(...) requires a config object literal" because Phase 3 anon-class synthesis (v0.5.172) now wraps closed-shape literals intonew __AnonShape_N(...). Theperry/ui App()handler still hard-matchedExpr::Object(props). Swapped to the already-existingextract_options_fieldshelper that handles both Object and AnonShape-New paths (the same oneperry/thread spawnuses). 19/27 doc-tests → 27/27. -
just_factorySIGABRT in cargo-test: a repro test for a separate pre-existing stack overflow (top-levelconst x = fn()+ factory returning a nested object literal) wasn't marked#[ignore]— so the default 2 MB test stack blew on every CI run. Marked#[ignore]with matching rationale to its sibling; real fix tracked separately. -
new Set([1,2,3]).has(1)returned undefined:refine_type_from_initemittedHirType::Named("Set")forExpr::SetNewFromArray, butis_set_expronly matchesHirType::Generic { base: "Set" }. Without explicit<number>type args, every Set.has()/add()/delete() fell through to the undefined-dispatch path. Same shape in Phase 4'sinfer_type_from_expr. Both fixed; Map/Set/WeakMap/WeakSet/Array/Promise now always flow as Generic (intrinsic generics can't be Named).
Known
test_edge_map_setparity: the simple Set.has regression is fixed, but the specificsetA.forEach((v) => setB.has(v))pattern has a second, path-dependent bug — same MD5 file passes from /tmp/ but the intersection comes back empty from the project root. Added toknown_failures.jsonwith the repro; tracked as follow-up.
Infrastructure
release-packages.yml'sawait-testsgate correctly blocked the v0.5.177 publish once Tests turned red — working as designed.
v0.5.177
Highlights
The long-hanging object-layout + type-inference arc lands (Phases 1/3/4/4.1 against the Static Hermes side-by-side), along with the first gap-tests bump in months (17 → 23 of 28), a benchmark refresh that finally beats Node on json_roundtrip (was Perry's one outstanding loss), and fixes for seven filed issues.
Fixes
- #142 —
Math.tan/asin/acos/atan/atan2were silent identity functions. Codegen now calls the existing runtime symbols. (ce550d9, via PR #148.) - #144 — TypeScript decorators were parsed into HIR but silently dropped at codegen. Compiler now hard-errors with a message naming the decoration point.
- #146 —
perry/threadprimitives (parallelMap/parallelFilter/spawn) compiled clean and silently returnedundefined. Wired up + added a compile-time mutable-outer-capture check with a broadened bypass-closure for named-function callbacks. - #150 —
Object.getOwnPropertyDescriptorreturnedundefinedon Phase 3 anon-shape literals. Scalar-replacement escape check now conservatively escapes all candidates on un-enumerated HIR variants. - #158 —
Map/Settreated-0and+0as distinct keys. Added SameValueZero normalization. - #20 —
console.trace()now emits a real native backtrace to stderr (needsPERRY_DEBUG_SYMBOLS=1for symbolicated frames). - Phase 3 follow-up — Error.cause AST extraction now handles
new Error(msg, { cause })shorthand and({ cause: e })paren-wrap (were silently losing.cause).
Features
- Phase 1 — structural shape inference for closed object literals in
infer_type_from_expr. - Phase 3 — anonymous class synthesis (
__AnonShape_N) for closed literals so property access hits the direct-GEP fast path. Backed by a synthesized constructor so per-literal values survive shape dedup. - Phase 4 / 4.1 — body-based return-type inference for free functions, class methods, getters, and arrow expressions; method-call return-type inference via a
class_method_return_typesregistry consulted at callsites. - Added an
extract_options_fieldshelper for builtin constructors (Response / Request / Headers / Error) that handles bothExpr::Objectand Phase 3'sExpr::New { __AnonShape_N }shapes.
Benchmarks
Reshot on current main. Perry wins every workload in the main suite vs Node.js and Bun (16 of 16 including json_roundtrip — 314 ms vs Node 377 ms, was 1.58× slower at v0.5.166). Polyglot: leads on 5 of 8 cells vs Rust / C++ / Go / Swift / Java; ties fibonacci with C++; trails by 1-2 ms on three stack-struct-friendly rows. README.md tables + benchmarks/baseline.json updated.
Infrastructure
- CLAUDE.md "Recent Changes" tightened to 1-2 line summaries.
- 40-test HIR integration suite added at
crates/perry-hir/tests/shape_inference.rscovering Phase 1/3/4/4.1 inference. - Four new follow-up issues filed (#154
await using, #155console.time, #156queueMicrotask/performance.now, #157 typed-array constructors, plus the existing #29 for lone-surrogate string handling) covering the remaining 5 failing gap tests.
Gap tests
22 / 28 → 23 / 28 passing vs node --experimental-strip-types. Remaining known-failing: async_advanced, console_methods, global_apis, string_methods, typed_arrays. All tracked in GitHub issues.
v0.5.164
Highlights
- #140 SIMD autovectorization restored on pure-accumulator loops.
loop_overhead32→12 ms,math_intensive48→14 ms,accumulate97→24 ms — all matching the v0.5.22 baseline exactly. Perry now beats Rust/C++ 7–8× onloop_overhead, 3× onmath_intensive, 4× onaccumulateagain. - V2.2 on-disk per-module object cache (
.perry-cache/objects/<target>/<key>.o). Warm-cache builds are ~29% faster; the real win isperry dev's watch-loop hot path where only the edited module recompiles.
Fixes
- #140 Scope the i32 shadow slot to locals actually used as array indices and refine the #74
asm sideeffectbarrier to skip bodies with outer-scope writes — the two changes that had knocked the LLVM vectorizer off pure-accumulator reductions between v0.5.91 and v0.5.162 (v0.5.164, 83f05ff) - #136
sendToClient(handle, msg)andcloseClient(handle)named imports from'ws'were silently no-ops — the receiver-less native dispatch table was missing entries (v0.5.162, d81acb5) - #135
--target webhangs whenbreak/continueis nested insideif/else/switch/try...catchinside a loop — WASM emitter was hardcodingBr(1)instead of consultingbreak_depth/loop_depth(v0.5.161, bf0a171) - PR #138 Windows vswhere discovery now requires VC tools (thanks @NicoAvanzDev) (9033dd4, 76f30c8)
- winget submission now syncs the
WINGET_PATuser's fork at runtime instead of hardcoding PerryTS's (v0.5.159, 58534df)
Features
- V2.2 per-module on-disk object cache closing #131, building on PR #132 — opt-outs via
--no-cache/PERRY_NO_CACHE=1; newperry cache info/perry cache cleansubcommands;PERRY_DEV_VERBOSE=1prints hit/miss ratios (v0.5.160, 1a12afe, 50d09ed)
Infrastructure
- Cache-key audit hardening: hashed codegen env vars by value (not presence),
.perry-cache/added to.gitignore, BSD-grep-portable smoke test regex (v0.5.160, 1a12afe) codegen-cache verbose outputaligned with the #131 spec (de514d5)
Docs
v0.5.158
Three versions' worth of fixes (v0.5.156–v0.5.158) plus the watch-mode AST cache and a perry check UX fix.
Fixes
--target web/ WASM — five compounding bugs (#133):str.charCodeAtand other un-special-casedString.prototypemethods fell through__classDispatch→ undefined;Math.sin/cos/tan/atan2/exp/… returned undefined because the WASM codegen had no handlers for the trig family;arr.push(1); arr.length === 0on top-levelconst arr = []because ArrayPush/Pop/Shift/Unshift/PushSpread/Splice only looked inlocal_map; Firefox NaN-canonicalization strippedSTRING/POINTER/INT32tag payloads across the JS↔WASM boundary (splitwrapForI64into legacy-rt + BigInt-safe-ffi variants);INT32_TAGconstants crashed wasm-bindgen with TypeError.--target android— obj.field returns NaN (#128): the PIC receiver guard (#51) and.lengthfast-path guard (#73) both used a Darwin-mimalloc-calibrated heap-window check. Bionic's Scudo allocator places allocations far below the 2 TB floor, so every real object pointer failed. Replaced with a platform-independent NaN-box tag check — same LLVM cost, works everywhere.perry check— emit file:line:column for HIR lowering errors (#129): previously every HIR-lowering error printed location-less. NewLowerErrortype carries anOption<Span>,lower_bail!macro wraps sites,check.rsdowncasts and attaches the span to the emitted diagnostic. Two sites migrated (super-prop access, binding patterns); infrastructure is in place for the remaining ~35 sites to land incrementally.- Release-packages
await-testsgate — query by filename, stop swallowing gh errors (v0.5.156): v0.5.155's gate timed out for 45 min with[]fromgh run list --workfloweven though the runs existed. Swapped togh api /repos/{repo}/actions/workflows/{file}/runsand dropped the2>/dev/null || echo '[]'that masked real API errors. Emergency-republish workflow_dispatch bypass preserved.
Features
perry dev— in-memory AST cache across rebuilds (#131, PR #132, by @TheHypnoo): new path-keyed content-addressedParseCacheowned byperry devfor the watch session's lifetime. Unchanged files reuse their prior AST; byte-identical content → hit regardless of mtime. Smoke test: after editing one file, rebuild is1/2 hit (1 miss)in 502 ms. Scope is strictlyperry dev—perry compileand other entrypoints passNoneand behave identically to v0.5.155.
Infrastructure
chore: gitignore benchmark binaries—image_conv,issue42_noallocno longer surface ingit status.
Full commit range: v0.5.155..v0.5.158.
v0.5.155
Highlights
- Tier-2 iOS simulator CI gate —
release-packages.ymlnow waits on bothTestsandSimulator Tests (iOS)green on the exact tag-commit SHA before publishing to brew / apt / npm. Newawait-testsjob pollsgh run list --commit <sha>every 30s with a 45-min deadline. Test workflow trigger extended totags: ['v*']so the Tests run actually exists on the tag.workflow_dispatchemergency-republish bypass preserved. - Doc-examples test harness + cross-compile matrix — Every
docs/examples/snippet now compiles under CI against macOS / Linux / Windows (blocking) and iOS / tvOS / watchOS / Android / WASM / Web (advisory until baselines blessed, now blocking on macOS+Linux). Simulator runs usePERRY_UI_TEST_MODE=1+ self-exit timer for bundle-launches smoke signal. - watchOS end-to-end —
--target watchos[-simulator]compiles and bundles; adds--features watchos-game-loop(Metal surface) and--features watchos-swift-app(SwiftUI host) plusperry.nativeLibrary.targets.<plat>.metal_sourcesfor.metallibbundling (closes #124). perry devwatch mode — New subcommand auto-recompiles on save (v0.5.143).- perry/ui gap closure —
alertWithButtons, string values inpreferencesGet/Set,onTerminate/onActivatelifecycle hooks, and LazyVStack backed byNSTableViewvirtualization (v0.5.151).
Features
feat: gate release-packages on Tests + Simulator Tests(v0.5.155)feat: close all 4 remaining perry/ui gaps(v0.5.151)feat: metal_sources manifest field for .metallib bundling(v0.5.146, closes #124)feat: compile-only banner + Fastify #125 regression check(v0.5.144)feat: perry dev — watch-mode subcommand for auto-recompile on save(v0.5.143, #126)feat: big doc-tests push — blessings + UI docs + xcompile + Android + PNG compress(v0.5.140)feat: tier-2 follow-ups — watchOS test mode + iOS-sim fix + simctl CI(v0.5.137)feat: wire PERRY_UI_TEST_MODE into perry-ui-ios + perry-ui-tvos(v0.5.135)feat: split xcompile into blocking (wasm+web) + advisory(v0.5.133)feat: bless gallery baselines + flip macOS+Linux blocking(v0.5.131)feat: doc-tests cross-compile matrix (iOS/tvOS/Android/WASM/Web)(v0.5.125)feat: doc-examples test harness + widget gallery(v0.5.123)feat: add --features watchos-swift-app for SwiftUI-hosted rendering(v0.5.122, #118)feat: add --features watchos-game-loop for Metal-surface apps(v0.5.114, #106)
Fixes
fix: drop run_with_timeout from simctl launch path(v0.5.154)fix: drop --console-pty from simctl-tests(v0.5.152)fix: honor --app-bundle-id on ios-simulator/ios full-app(v0.5.150)fix: ios-simulator Info.plist platform keys(v0.5.149)fix: simctl env vars via SIMCTL_CHILD_ prefix(v0.5.148)fix: simctl --setenv arg syntax(v0.5.147)fix: simctl-tests — detect timeout/gtimeout + fallback(v0.5.145)fix: async arrow/closure returns — wrap in js_promise_resolved(v0.5.142, closes #125)fix: cross-platform fs/roundtrip path(v0.5.142)fix: Ubuntu gtk4 border symbols + Windows png encoder borrow(v0.5.141)fix: PowerShell arg-splitting, round two — repeat xcompile flag(v0.5.139)fix: PowerShell arg-splitting on Windows xcompile step(v0.5.138)fix: three follow-ups from v0.5.135 CI run(v0.5.136)fix: Windows Picker parent HWND for WS_CHILD(v0.5.132)fix: force async-runtime feature when UI backend is linked(v0.5.130)fix: diagnose why --whole-archive didn't surface on Ubuntu(v0.5.129)fix: use --whole-archive for stdlib on Linux UI links(v0.5.128)fix: Linux link order + Windows MSVC env(v0.5.127)fix: merge stdout + stderr in doc-tests compile-fail detail(v0.5.126)fix: watchos bundle step copies project assets into the .app(#123)fix: platform-split HEAP_MIN for android/linux(v0.5.125)fix: doc-tests CI follow-ups after first run of v0.5.123(v0.5.124)fix: reject silent perry/ui API misuse at compile time(v0.5.119, #114)fix: drop libc: ["glibc"] from glibc Linux npm manifests(v0.5.118, #116)fix: wire URL/URLSearchParams through LLVM backend(v0.5.117, #111)fix: animateOpacity/animatePosition signature, units, and reactivity(v0.5.116, #109)fix: console.log on Windows by gating MSVC subsystem on needs_ui(v0.5.114, #108)fix: find_native_library target_key honors watchos(v0.5.115, #107)fix: make --target watchos[-simulator] compile end-to-end(v0.5.113, #105)fix: perry-ui-ios StringHeader.length migrated to byte_lenfix: ios-game-loop entry point for iOS/tvOS targets
Infrastructure / Docs
docs(skill): rewrite /release to tag HEAD, not commit uncommitted workchore: simctl-tests per-phase trace + explicit timeouts(v0.5.153)docs: correct stale version strings + TabBar platform caveatdocs: port-npm-to-perry skill + porting guide(experimental, re #115)docs: add CONTRIBUTING, CoC, and issue/PR templatesdocs: carve out "no version bumps in external PRs" ruledocs: add perry dev to CLI commands referencedocs: rewrite ui/widgets.md against the real API(v0.5.134)docs: document @perryts/perry npm install path in README + mdBook
v0.5.112
Bug Fixes
- **Auto-reactive
Text(\...${state.value}...`)in HIR lowering (#104)** — the docs atstate.mdpromised template literals containingstate.valuereads would bind reactively, but no backend emitted the binding;count.set(...)fired subscribers, nothing had ever subscribed. Fixed incrates/perry-hir/src/lower.rsby desugaringText(Tpl-with-state)into an IIFE closure that creates the widget, registersstateOnChangeper distinct state (each capturing the widget handle and re-rendering viatextSetString), and returns the handle. The outer IIFE is required becausecollect_module_let_idsonly tracksStmt::Let; bareLocalSet/LocalGetinsideExpr::Sequenceat module top level had no backing WASM global or local and returnedTAG_UNDEFINED`, silently dropping the widget. - Wasm codegen
collect_strings_in_exprtraversesExpr::Sequence— was aUpdate | Sequence(_) => {}catch-all. Nested bridge-function names (perry_ui_text_create,perry_ui_state_on_change,perry_ui_text_set_string) and string literals ("Count: ") were dropped from the WASM string table, so the memcalls resolved to the empty name slot. - Wasm
map_ui_methodaccepts"stateOnChange"— previously only"onChange"/"state_on_change"; the LLVM-side camelCase naming didn't round-trip to wasm.
Verification
- Web/wasm: jsdom click-through —
Text(\Count: ${count.value}`)` → 3 × Increment → "Count: 3". - Multi-state:
test_ui_state_binding.ts(4 different template forms sharing onecountstate) all update together through +1 and -1 clicks. - macOS native: AppleScript driving AppKit window confirms reactive updates.
- iOS/tvOS/watchOS/Android/GTK4/Windows: statically audited — identical FFI signatures (
perry_ui_state_on_change(i64, f64),perry_ui_text_set_string(i64, i64)), real (non-stub) bodies, samejs_closure_call1extern-C ABI. Not exercised on hardware.
v0.5.110 — perry/ui ForEach codegen
Followup to #103. v0.5.109 fixed the type stubs + docs; this release fixes the actual codegen so the todo app visibly shows a window.
Bug Fixes
-
ForEach(state, render)fromperry/uiwas unimplemented in codegen. The generic dispatch path emittedperry/ui warning: method 'ForEach' not in dispatch table (args: 2)and returned 0/undefined. The outerVStack'swidget_add_childthen got an invalid handle, AppKit silently refused to attach the window body, and the process ran withlsappinfo type="BackgroundOnly"— CPU burning, no window. Added a special case incrates/perry-codegen/src/lower_call.rs(next toVStack/HStack) that synthesizes aperry_ui_vstack_create(8.0)container, callsperry_ui_for_each_init(container, state_handle, render_closure), and returns the NaN-boxed container pointer.Why it's a special case and not a
UiSigtable entry: same reason asVStack/HStack— variadic-in-shape (needs side-effectful container synthesis + separate for_each_init call + handle return), which doesn't fit the uniform args→runtime_fn table.
Verified
test_min4.ts(VStack containing ForEach overState<string[]>) launches withtype="Foreground".- The corrected todo app from the
docs/src/ui/state.md"Complete Example" compiles and launches withtype="Foreground".
v0.5.109 — perry init type stubs + UI docs
Bug Fixes
-
perry initTypeScript type stubs were broken for non-numeric State (closes #103).types/perry/ui/index.d.ts:Stateis now generic (State<T = number>).State<string[]>([])andState("")from the docs now type-check instead of erroring on the old number-only signature.- Added the
ForEach(count: State<number>, render)export — it was used in the docs todo-app example but missing from the stub. stateBindTextfieldnow takesState<string>to match its purpose.
-
UI docs examples called a
TextField(state, placeholder)form that didn't exist and segfaulted at launch.UiArgKind::Stratcrates/perry-codegen/src/lower_call.rs:2557routes the first arg throughget_raw_string_ptr, so a State handle (i64) reinterpreted as a NaN-boxed string derefs garbage. Rewrote the examples in:docs/src/getting-started/first-app.md— counter + todo appdocs/src/ui/state.md— two-way binding, onChange, complete exampledocs/src/ui/widgets.md—TextField/SecureField/Toggle/Slider/Picker/ Formdocs/src/ui/dialogs.md— the text-editorTextFieldline
to use the actual runtime signatures:
TextField(placeholder, onChange),Slider(min, max, onChange),Picker(onChange)+pickerAddItem, etc..onChangemethod replaced with the free-functionstateOnChange(state, cb).
Verified
5 example binaries built from the corrected docs (counter, todo, controls, form, onchange) pass tsc --noEmit against the regenerated stubs AND compile + launch without crashing via perry src.ts -o bin.
No runtime/codegen change — stubs are embedded into the perry binary via include_str! at compile time and ship to users through perry init / perry types.