SettingsActivity: render ui_options label instead of raw pref value#137
Conversation
Settings rows with `ui: "radiobuttons"` or `"dropdown"` previously
rendered the raw stored pref value in the value label beneath the
title. So a Lightning Piggy Hero Image row with
`ui_options=[("Lightning Piggy", "lightningpiggy"), ...]` displayed
"lightningpiggy" — the internal value with no space — instead of
"Lightning Piggy".
The label-to-value mapping only ever existed in the picker activity
itself (the radio buttons that show "Lightning Piggy"); the list-view
row had no access to it and fell back to the raw value. Same for the
post-save update path — after the user picked an option, the framework
wrote `new_value` directly to the value_label, so even a freshly-saved
row showed "lightningpiggy" again.
This patch adds a small `_value_label_for(setting, stored_value)`
helper in `settings_activity.py` that looks up `stored_value` in
`setting["ui_options"]` and returns the matching label if present
(raw value otherwise). Both the list-view row render
(`settings_activity.py`) and the picker post-save
(`setting_activity.py`) route through this mapping so the row stays
consistent before and after a save.
Behaviour:
- ui_options entry matches stored value → display its label
- No ui_options → display raw value (unchanged from before)
- Stored value not in ui_options (stale pref after option-list change)
→ display raw value (visible, recoverable, not silently dropped)
Tests
-----
6 unit tests in tests/test_settings_activity_label_mapping.py covering
matched values, missing values, missing ui_options, empty ui_options,
None values, and first-match-wins for duplicate values.
$ bash tests/unittest.sh tests/test_settings_activity_label_mapping.py
Ran 6 tests
OK
Motivating case in the wild: LightningPiggyApp PR MicroPythonOS#26 (multi-wallet)
adds per-slot hero image with values "lightningpiggy" /
"lightningpenguin" / "none" — without this framework fix the
Customise menu showed those raw values, no spaces.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
CONTRIBUTING.md merge-checklist auditWalking through each item explicitly so the maintainer doesn't have to re-derive it:
AGENTS.md style:
Migration note (item 5)No pref migration is required — this PR is display-only. What changes:
Apps that depend on the displayed row text matching the raw pref value (e.g. UI tests grepping screenshots for If a user's stored pref value is no longer present in the current Companion docs PR: MicroPythonOS/docs#7 — ready to merge in lockstep with this one. |
Hardware verification logDevice under test: ESP32 multi-wallet LP build, Step 1 — Confirm the bug exists with the frozen frameworkNavigated home → ⚙ → Customise. The Hero Image row's secondary text rendered the raw stored pref value: Screenshot saved to Step 2 — Inject the patched modules at runtimeimport mpos.ui.settings_activity as s
import mpos.ui.setting_activity as sa
with open('/tmp/sa_new.py') as f:
exec(f.read(), s.__dict__)
with open('/tmp/sa_picker_new.py') as f:
exec(f.read(), sa.__dict__)
assert s._value_label_for(
{'ui_options':[('Lightning Piggy','lightningpiggy')]},
'lightningpiggy'
) == 'Lightning Piggy' # passesStep 3 — Apply
|
|
I think this might be breaking some unit tests but I'll merge and fix myself :-) thanks! |
Summary
Settings rows with
ui: "radiobuttons"(or"dropdown") render the raw stored pref value in the secondary text beneath the title. So a row with:{"title": "Hero Image", "key": "hero_image", "ui": "radiobuttons", "ui_options": [("Lightning Piggy", "lightningpiggy"), ("Lightning Penguin", "lightningpenguin"), ("None", "none")]}…displays "lightningpiggy" (internal value, no space) in the row, even though the picker activity itself correctly shows "Lightning Piggy" on the radio button. The label-to-value mapping only ever existed inside the picker; the list-view row had no access to it.
Same gap on the post-save path — when the user picks an option, the framework writes
new_valuedirectly to the value_label, so even a freshly-saved row reverts to "lightningpiggy".Fix
Adds a small helper in
settings_activity.py:Routed through in two places:
settings_activity.pylist-view render — instead ofvalue_text = stored_value, usevalue_text = _value_label_for(setting, stored_value). Default-value branch also routed through.setting_activity.pypost-save — instead ofvalue_label.set_text(new_value), look up the label fromui_optionsfirst.ui_optionsmatches stored value"lightningpiggy""Lightning Piggy"ui_options(textarea, etc.)ui_options(stale pref)default_valuerendered with"(defaults to X)"default_valueTests
tests/test_settings_activity_label_mapping.py— 6 unit tests in one class on the new helper:ui_optionspasses through (textarea / freeform settings unaffected)ui_optionslist passes throughNonevalue passes through (defensive — caller handles "(not set)" elsewhere)Motivating case in the wild
LightningPiggyApp PR #26 (multi-wallet) adds a per-slot Hero Image setting with values
"lightningpiggy"/"lightningpenguin"/"none". Without this framework fix, the Customise menu showed those raw values with no space ("lightningpiggy"instead of"Lightning Piggy").I shipped a local closure-callback workaround there as a stopgap; once this PR lands, that workaround can come out and the row will be consistent on both initial display and post-save.
Hardware verification (this commit unchanged — verification done on existing tree)
Captured on a live device running multi-wallet LP with the unpatched (frozen) framework, then with the patched framework injected via
exec(src, mpos.ui.settings_activity.__dict__)at the REPL.Before —
lightningpiggyraw value visible in the Hero Image row of Customise → exactly the bug this PR fixes:After (label re-rendered with
_value_label_forapplied to the live widget):PNG screenshots:
/tmp/lp_customise_before_137.pngand/tmp/lp_customise_after_137_simulated.pngon the development host — see follow-up comment for the verification log.Test plan
ui_options(textarea / freeform) — verified by the "passes through" tests_value_label_foris applied to the same widget (see screenshots in the verification comment below)label.set_text(_value_label_for(...))); a single end-to-end "open Customise on a fresh boot of firmware that includes this PR" check will tick once SettingsActivity: render ui_options label instead of raw pref value #137 is in a release build🤖 Generated with Claude Code