8000 Apple RadioGroup and Stepper: radio NSButtons / an iOS checkmark list; NSStepper / UIStepper (#150) by rleibman · Pull Request #153 · rleibman/thicket · GitHub
[go: up one dir, main page]

Skip to content

Apple RadioGroup and Stepper: radio NSButtons / an iOS checkmark list; NSStepper / UIStepper (#150) - #153

Merged
rleibman merged 4 commits into
feat/radio-stepperfrom
issue/150-apple-radio-stepper
Nov 4, 2026
Merged

rleibman merged 4 commits into
feat/radio-stepperfrom
issue/150-apple-radio-stepper

Conversation

@rleibman
@rleibman rleibman commented Nov 4, 2026
Copy link
Copy Markdown
Owner

Closes #150. Stacked on #151 (feat/radio-stepper).

What

  • Shim kinds 26 RadioGroup and 27 Stepper in Abi.kinds, both shims and the generated ShimKind. AppleRenderer.kindCode now maps the two widgets to these kinds instead of to Picker and Slider.
  • RadioGroup. On AppKit (RadioGroupView) it is a vertical stack of NSButton(radioButtonWithTitle:), and AppKit groups them by their shared superview. On UIKit (RadioListView) it is a checkmark list: RadioRow buttons with a trailing checkmark on the chosen row, and the exclusion is the construction's. Both are driven by the existing picker functions (sui_picker_*, sui_set_selected under suppression, sui_on_select, sui_option_*, sui_shown_option, sui_choose).
  • Stepper. On AppKit (StepperView) it is an NSStepper (valueWraps off) beside a label. On UIKit it is a UIStepper beside a UILabel. Both are driven by the slider's functions plus a new sui_set_step(by, digits). digits is Steps.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 .valueChanged only when the value moved, as the checkbox click does.
  • Gallery (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 final GallerySelfTest in the working tree, the version committed in 5aceeaa. The commit after the run changes only docs.
  • iOS simulator gallery: 246, ALL CHECKS PASSED.
  • macOS gallery: 231 on each of its two runs, ALL CHECKS PASSED. The screen was locked, so the lifecycle checks were skipped. Unlocked, before the grouping check was added: 234.
  • shimGen/testFull: 51/51. shimGen/run --check: current.
  • What the checks read:
    • The radio controls are 3 × NSButton / 3 × RadioRow, with Medium chosen.
    • Clicking Large: "Size: Large", and only Large chosen.
    • The steppers' labels read 2 / 1.50, then 3 / 1.75 after +, back to 1.50 after −, and the count stops at 5.
    • An app write of Selected(0) or Value(1) is not reported back.
    • New options keep the index, and -1 shows 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:

    • macOS: each button in its own superview.
    • iOS: the tap handler doesn't clear the other rows.

    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 (digits 0): the precision check fails (shows List(2, 2)). macOS only; the digits come from shared Scala.

Notes

  • Kind-code collision risk with Apple embedding point: host a platform view (WidgetKind.Platform / Prop.View) in the AppKit and UIKit shims #145. That work may also claim code 26 for Platform. Whichever PR lands second renumbers. The Swift case bodies are self-contained, so this costs one line each in Abi.kinds and the two sui_create switches.
  • Base has moved. feat/radio-stepper gained ce1ea75 after this branch was cut. It touches only Element.scala, RadioStepperSpec, Android's StepperView and Steps (isFinite), none of the files here, so I didn't rebase: that would need a force-push.
  • Out of scope, not changed: scalafmtAll on the Mac reflows a require(...) in Element.scala (from RadioGroup and Stepper, the last two v1 widgets, as §12.2a exceptions #151). I reverted that rather than include it.
  • One earlier iOS run gave no verdict. It stopped after the camera "photo" drive step (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.
  • Not measured: a physical device, and VoiceOver reading the checkmark rows (the selected trait is UIButton's own).

🤖 Generated with Claude Code

Roberto Leibman and others added 2 commits November 3, 2026 19:15
…; 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>
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.Stepper to these kinds in AppleRenderer.
  • Implement AppKit and UIKit shim views for RadioGroup and Stepper, including stepper precision support via sui_set_step(by, digits) and test-driving via sui_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

rleibman and others added 2 commits November 4, 2026 11:01
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>
@rleibman
rleibman merged commit aa67db7 into feat/radio-stepper Nov 4, 2026
1 check failed
rleibman added a commit that referenced this pull request Nov 4, 2026
…#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>
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

0