Repository navigation
Apple RadioGroup and Stepper: radio NSButtons / an iOS checkmark list; NSStepper / UIStepper (#150) - #153
Conversation
…; gallery checks (#150) macOS gallery 234 ok, ALL CHECKS PASSED (12 new: section, 6 RadioGroup, 5 Stepper). iOS not yet run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A last radio click with a handler that only records, so the platform's grouping is measured apart from the app's write-back, which re-selected exclusively and hid an ungrouped set. bin/verify-apple.sh "iPhone Air": 20/20 steps. iOS simulator gallery 246, macOS 231 (screen locked), 13 new on each. shimGen/testFull 51/51. Falsified: ungrouped radios (macOS: separate superviews; iOS: no exclusion) fail the new check. Range +100 fails 'stops at 5' (7). An echoed app write fails both 'not reported back' checks (macOS). 0 digits fails the precision check (macOS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Note
Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.
🟡 Changes recommended
Both AppKit and UIKit StepperView.show() still use locale-sensitive String(format:) with printf-style rounding, which can produce step labels inconsistent with the shared Scala Steps.format (HALF_UP, locale-stable) rule and needs alignment via decimal rounding/formatting.
2 open issues
What changed in this PR
This pull request adds Apple-native implementations of RadioGroup and Stepper to the Apple renderer/shims by introducing dedicated shim kinds (26/27), wiring them through AppleRenderer, and extending the Apple gallery self-tests and documentation to cover/reflect the new behavior.
Changes:
- Add shim kinds 26 (RadioGroup) and 27 (Stepper), update ABI/shim-gen output, and map
WidgetKind.RadioGroup/WidgetKind.Stepperto these kinds inAppleRenderer. - Implement AppKit and UIKit shim views for RadioGroup and Stepper, including stepper precision support via
sui_set_step(by, digits)and test-driving viasui_step(h, forward). - Extend Apple gallery self-tests (radio/stepper checks) and update docs/README/status to reflect the new coverage.
Assessment: This pull request requires changes.
| File | Summary |
|---|---|
tools/shim-gen/src/main/scala/thicket/tools/shim/Abi.scala |
Adds shim kinds 26/27 and stepper ABI docs/functions (sui_set_step, sui_step). (Nit: align sui_step doc with actual semantics.) |
README.md |
Updates project/status text to reflect Apple RadioGroup/Stepper coverage. |
modules/renderer-apple/src/main/scala/thicket/renderer/apple/Shim.scala |
Declares new externs and ShimKind constants for RadioGroup/Stepper and stepper functions. |
modules/renderer-apple/src/main/scala/thicket/renderer/apple/AppleRenderer.scala |
Maps widget kinds to new shim kinds and wires Prop.Step via sui_set_step. |
modules/renderer-apple/src/main/scala/thicket/renderer/apple/AppleInspect.scala |
Adds inspect helper to step steppers through the shim (sui_step). |
modules/renderer-apple/shim/Sources/Shim+UIKit.swift |
Implements UIKit RadioListView/RadioRow and StepperView, integrates with picker/slider plumbing and stepper ABI. (Moderate: enabled propagation for composite views; formatting should match shared Steps rounding/locale; accessibility .selected trait handling.) |
modules/renderer-apple/shim/Sources/Shim+AppKit.swift |
Implements AppKit RadioGroupView and StepperView, integrates with picker/slider plumbing and stepper ABI. (Moderate: formatting should match shared Steps rounding/locale.) |
modules/renderer-apple/shim/include/thicket_apple.h |
Regenerated header for new kinds and stepper API. (Nit: update sui_step comment to match real behavior.) |
examples/gallery/README.md |
Documents the new Apple RadioGroup/Stepper self-test checks and updated counts. |
examples/gallery/apple-shared/src/main/scala/example/gallery/GallerySelfTest.scala |
Adds radioStepperChecks and integrates them into the Apple self-test run. |
docs/decisions.md |
Records decision/measurement notes for Apple RadioGroup/Stepper implementation. |
docs/12-component-status.md |
Updates component status to “done” for RadioGroup/Stepper on AppKit/UIKit and refreshes related status counts. |
Review effort: Balanced
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…#151) * RadioGroup and Stepper, the last two v1 widgets, as §12.2a exceptions The owner's decision (2026-11-03): build both, each platform's own control where it has one and a construction where it has none; iOS's Radio is a checkmark list. - renderer-api: WidgetKind.RadioGroup, WidgetKind.Stepper, Prop.Step, and Steps.format (a value to its step's precision). core: dsl.RadioGroup (Picker's props) and dsl.Stepper (Slider's, plus the step). - GTK: grouped GtkCheckButtons (the segmented control's code, generalised) and a GtkSpinButton with digits from the step. - Android: a vertical RadioGroup (the segmented control's code) and a StepperView, − / value / + from framework views, clamped. - Apple: interim mapping to the picker and the slider until #150. - Gallery: a RadioGroup and Stepper section, with GTK and Android checks. Measured: checkLinux exit 0; RadioStepperSpec 4/4 on JVM, Native and JS (core 176). GTK gallery 150 (144 + 6), todo 69; Android gallery 100 (95 + 5) on s2test; platforms/Test/compile 0 errors. Falsified: radio buttons ungrouped fails "only it active" (GTK); no spin digits fails the precision check (GTK); on Android the wrong precision fails it and no clamp fails "stops at its maximum". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Update format method to check for NaN and Infinite Handle NaN and Infinite values in format method. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Add validation for min, max, and step in Element Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update setStep to handle NaN and infinite values Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Tighten Stepper validation and finite formatting Co-authored-by: rleibman <3268890+rleibman@users.noreply.github.com> * Apple RadioGroup and Stepper: radio NSButtons / an iOS checkmark list; NSStepper / UIStepper (#150) (#153) * WIP Apple RadioGroup and Stepper: kinds 26/27, sui_set_step, sui_step; gallery checks (#150) macOS gallery 234 ok, ALL CHECKS PASSED (12 new: section, 6 RadioGroup, 5 Stepper). iOS not yet run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Apple RadioGroup and Stepper: the grouping check, docs (#150) A last radio click with a handler that only records, so the platform's grouping is measured apart from the app's write-back, which re-selected exclusively and hid an ungrouped set. bin/verify-apple.sh "iPhone Air": 20/20 steps. iOS simulator gallery 246, macOS 231 (screen locked), 13 new on each. shimGen/testFull 51/51. Falsified: ungrouped radios (macOS: separate superviews; iOS: no exclusion) fail the new check. Range +100 fails 'stops at 5' (7). An echoed app write fails both 'not reported back' checks (macOS). 0 digits fails the precision check (macOS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Update label formatting to use NSDecimalNumber Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Refactor show function to use NSDecimalNumber for rounding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Roberto Leibman <rleibman@magrathea.local> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rleibman <3268890+rleibman@users.noreply.github.com> Co-authored-by: Roberto Leibman <rleibman@magrathea.local>

Closes #150. Stacked on #151 (
feat/radio-stepper).What
RadioGroupand 27StepperinAbi.kinds, both shims and the generatedShimKind.AppleRenderer.kindCodenow maps the two widgets to these kinds instead of to Picker and Slider.RadioGroupView) it is a vertical stack ofNSButton(radioButtonWithTitle:), and AppKit groups them by their shared superview. On UIKit (RadioListView) it is a checkmark list:RadioRowbuttons with a trailingcheckmarkon the chosen row, and the exclusion is the construction's. Both are driven by the existing picker functions (sui_picker_*,sui_set_selectedunder suppression,sui_on_select,sui_option_*,sui_shown_option,sui_choose).StepperView) it is anNSStepper(valueWrapsoff) beside a label. On UIKit it is aUIStepperbeside aUILabel. Both are driven by the slider's functions plus a newsui_set_step(by, digits).digitsisSteps.digits(by), computed in Scala, so the label follows the shared rule.sui_step(h, forward), for the self-test. On AppKit it presses the arrow's accessibility element (NSAccessibilityStepperArrowButton, what VoiceOver presses). Measured:NSStepper.accessibilityPerformIncrement()moves nothing, and a synthesised mouse-down moved the value only in the half opposite the arrow it was aimed at. On UIKit it writes value ±stepValue, which the control's setter clamps, and sends.valueChangedonly when the value moved, as the checkbox click does.GallerySelfTest.radioStepperChecks): 13 checks, the same as GTK's and Android's plus the ones below.Measured
bin/verify-apple.sh "iPhone Air": ALL STEPS PASSED, 20/20. That run was on 2a1e900 with the finalGallerySelfTestin the working tree, the version committed in 5aceeaa. The commit after the run changes only docs.shimGen/testFull: 51/51.shimGen/run --check: current.NSButton/ 3 ×RadioRow, with Medium chosen.2/1.50, then3/1.75after +, back to1.50after −, and the count stops at 5.Selected(0)orValue(1)is not reported back.-1shows none chosen.Falsified
Radios ungrouped fails the new check "with nothing written back, the group itself leaves only the clicked one chosen" (
chosen List(true, false, true), shows 'Small, Large'), on macOS and on iOS:This check was needed. The old "only Large stays chosen" check still passed with the radios ungrouped, because the app's write-back (
Selected(2)) re-selects exclusively. Also measured: on macOS 27, giving each button a different action did not ungroup them.No clamp (range max + 100): "the count stops at 5" fails with
value 7.0, on macOS and on iOS.An app write echoed as an event (the shim fires the callback in
sui_set_selected/sui_set_value): both "not 8000 reported back" checks fail (Size: Small,Count: 1). macOS only.Wrong precision (
digits0): the precision check fails (shows List(2, 2)). macOS only; the digits come from shared Scala.Notes
Platform. Whichever PR lands second renumbers. The Swiftcasebodies are self-contained, so this costs one line each inAbi.kindsand the twosui_createswitches.feat/radio-steppergained ce1ea75 after this branch was cut. It touches onlyElement.scala,RadioStepperSpec, Android'sStepperViewandSteps(isFinite), none of the files here, so I didn't rebase: that would need a force-push.scalafmtAllon the Mac reflows arequire(...)inElement.scala(from RadioGroup and Stepper, the last two v1 widgets, as §12.2a exceptions #151). I reverted that rather than include it.no Use Photo), with all 13 new checks passed. The falsified run and the verify run both reached a verdict, so I take it as a camera-step flake, not this change.🤖 Generated with Claude Code