Skip to content

fix(web-ui): Picker(onChange) receives its callback - #14692

Open
raliclo wants to merge 2 commits into
PerryTS:mainfrom
raliclo:fix/web-ui-picker-callback
Open

raliclo wants to merge 2 commits into
PerryTS:mainfrom
raliclo:fix/web-ui-picker-callback

Conversation

@raliclo

@raliclo raliclo commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

On --target web, Picker(onChange) never calls onChange: choosing an option does nothing. perry/ui declares Picker(onChange: (index: number) => void), so codegen passes the closure as the first argument of perry_ui_picker_create in crates/perry-codegen-wasm/src/wasm_runtime.js, but that bridge takes (items_json, selected, callback), so the closure landed in items_json and callback stayed undefined. A closure in the first slot is now taken as onChange; the three-argument shape still works. The new __isPerryClosure recognises a {funcIdx} handle object or a host function.

Changes

  • crates/perry-codegen-wasm/src/wasm_runtime.js: perry_ui_picker_create accepts Picker(onChange); new __isPerryClosure.
  • crates/perry-codegen-wasm/src/emit/tests.rs: picker_on_change_is_called_with_the_chosen_index, plus the shared run_web_ui_source_then helper (below).

Related issue

n/a (found while porting a filter page to --target web).

Test plan

macOS 27.3 arm64, Node 26.12.1, nightly from rust-toolchain.toml. main below is fee15e5049.

cargo test -p perry-codegen-wasm
main this branch
new test FAIL: left: "" PASS
mutation: this branch with only wasm_runtime.js reverted to main new test FAILS again; restoring the file (same shasum) passes it
perry-codegen-wasm 76 passed 77 passed

Also run, main vs the five web-ui fixes merged together (each fix touches only its own runtime function):

main with the fixes
rustfmt --check --edition 2021 crates/perry-codegen-wasm/src/emit/tests.rs clean
cargo clippy -p perry-codegen-wasm --tests (warnings located in the crate) 12 the same 12, none in emit/tests.rs
tests/integration/wasm/run_wasm_tests.sh 30 pass, 3 fail (04_functions, 10_higher_order, 19_ffi_imports) identical
SKIP_COMPILE_GATES=1 scripts/run_lint_gates.sh 20 of 142 FAIL the same 20

Browser check: the program below compiled with perry compile f.ts --target web -o f.html, loaded in headless Chrome (--headless=new --dump-dom) with a small script appended that selects the second option, dispatches change and reads the log text:

import { App, VStack, Text, Picker, pickerAddItem, setText } from "perry/ui"
const picker = Picker((index: number) => { setText("log", "picker " + index) })
pickerAddItem(picker, "first")
pickerAddItem(picker, "second")
App({ title: "picker", width: 300, height: 200, body: VStack(8, [picker, Text("(no callback)", "log")]) })
  • main: log=(no callback)
  • this branch: log=picker 1

I did not run the full workspace test command from the template.

Shared test helper

emit/tests.rs gains run_web_ui_source_then, which boots a compiled program against a small element tree (WIDGET_DOM: children, text, listeners fired by dispatch(type), descendant tag selectors for querySelector) instead of the flat stub, so a test can build a widget, send it an event and read the DOM it produced. The existing run_web_source_then keeps the flat stub. The same helper hunk is byte-identical in the sibling web-ui PRs (Toggle, Picker, Section, Image, Table), and each test sits at a different spot in the file, so the five merge in any order without conflicts; I checked this with an octopus merge of all five branches onto main (clean, 81 tests pass).

Checklist

  • I have NOT bumped the workspace version or edited CHANGELOG.md (maintainer handles these at merge)
  • My commits follow the loose feat: / fix: / docs: / chore: prefix convention used in the log
  • I've read CONTRIBUTING.md and agree to the Code of Conduct

🤖 Generated with Claude Code

https://claude.ai/code/session_01CUQanVsnitcrmYLAWWPc3p

Summary by CodeRabbit

  • Bug Fixes
    • Fixed web Picker so the callback-first form works: selecting an option now calls the callback with the chosen index.

ralic and others added 2 commits November 14, 2026 21:37
perry/ui declares `Picker(onChange)`, so codegen hands the closure to
`perry_ui_picker_create` as its first argument, which the web runtime named
`items_json`; the callback slot stayed undefined and choosing an option never
called back. A closure in the first slot is now taken as onChange; the
three-argument (items_json, selected, callback) shape still works.
`__isPerryClosure` recognises a `{funcIdx}` handle object or a host function.

emit/tests.rs gains run_web_ui_source_then (shared with the Toggle fix), which
boots a program against a small element tree so a test can send a widget an
event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CUQanVsnitcrmYLAWWPc3p
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CUQanVsnitcrmYLAWWPc3p
@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Explain Change →

Walkthrough

The web runtime now accepts Picker(onChange) when the callback is the only argument. A test selects the second option, dispatches a change event, and checks that the callback receives index 1.

Changes: Picker callback handling

Layer / File(s) Summary
Callback-first Picker handling
crates/perry-codegen-wasm/src/wasm_runtime.js, changelog.d/14692-web-ui-picker-callback.md
The runtime identifies functions and Perry closure objects as callbacks. When the third argument is omitted, perry_ui_picker_create treats the first argument as the callback. The changelog describes the web callback behavior.
DOM test support and callback test
crates/perry-codegen-wasm/src/emit/tests.rs
The test helpers support configurable DOM stubs and a fake element tree. The Picker UI test changes the selected option, dispatches change, and checks that the callback logs index 1.

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Low

Suggested reviewers: thehypnoo

Merge Risk: Low · up to bb314

Picker(onChange) under the web target now calls its callback with the chosen index. A small inconsistency remains in how raw host functions are accepted but then ignored; no current caller is known to trigger it. This is low risk to merge.

Pre-merge checks: Passed 4

Check name Status Explanation
Title check Passed The title names the main change: Picker(onChange) now receives its callback in the web UI target. It uses the conventional fix(web-ui) prefix and matches the changelog entry, the wasm_runtime.js chang…
Description check Passed The description follows the template's main sections: Summary, Changes, Related issue, Test plan, and Checklist. The Summary explains the bug and the fix. Changes lists both touched files. The Related…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: Picker(onChange) now receives its callback in the web UI target. It uses the conventional fix(web-ui) prefix and matches the changelog entry, the wasm_runtime.js change, and the new test. It is 51 characters and 5 words, so it stays concise.

Full details: Description check

Explanation

The description follows the template's main sections: Summary, Changes, Related issue, Test plan, and Checklist. The Summary explains the bug and the fix. Changes lists both touched files. The Related issue is marked n/a with a reason. The Test plan gives concrete commands, results, and a browser check. The template's Screenshots section is optional and is omitted, which is acceptable. The checklist items are checked. The test plan does not tick the template's workspace-wide test command, and the author says that command was not run. The template asks that boxes pass before merge, so this gap is worth noting. The description does not mention the template's Cargo.toml version and CHANGELOG rules beyond the checklist, which the author did confirm. The description also includes a changelog-related commit in the objectives, which the description does not mention, but this does not affect the check.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@raliclo

raliclo commented Nov 14, 2026

Copy link
Copy Markdown
Contributor Author

Overlap with #14664: both PRs add function __isPerryClosure to wasm_runtime.js. #14664's version checks the __perryClosures WeakSet that __newClosure fills; this PR's checks for a funcIdx field or a host function. The two merge without a textual conflict, but JS keeps only the later declaration (this PR's), and then #14664's a_closure_used_as_a_value_is_a_function fails (git merge of both heads, cargo test -p perry-codegen-wasm: 79 passed, 1 failed).

Resolution: the merged tree needs only #14664's definition. With this PR's 7-line __isPerryClosure block removed, the same merged tree passes 80 of 80, including picker_on_change_is_called_with_the_chosen_index, because the closure Picker receives is the object __newClosure registered. So whichever lands second should drop this PR's definition: if #14664 lands first, this PR should not add its own; if this PR lands first, #14664 should replace it.

@coderabbitai coderabbitai 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.

Additional comments: 1

crates/perry-codegen-wasm/src/wasm_runtime.js-2863-2863: 1

2863-2863: Minor · Functional Correctness · Quick win

Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.

Handle direct host functions in callWasmClosure.

This predicate accepts a host function, and the new Picker branch stores that function as _perryCallback. However, callWasmClosure initializes closure only for object or number inputs. A direct function returns at Line 2877 without invocation.

Accept function inputs in that resolver, and add a host-function regression case. The new test covers only compiled closure objects.

Review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a58466fb-2994-45f0-8431-ee57a91e029c
Commits

Reviewing files that changed from the base of the PR and between fee15e5 and bb31449.

Files selected for processing: 3

  • changelog.d/14692-web-ui-picker-callback.md
  • crates/perry-codegen-wasm/src/emit/tests.rs
  • crates/perry-codegen-wasm/src/wasm_runtime.js

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

This branch has not been deployed

No deployments
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