fix(clients/settings): controller settings that can't do anything no longer look live #50

Merged
enricobuehler merged 1 commits from worktree-haptics-m11-settings into main 2026-08-04 21:08:20 +00:00
Owner

Haptics sweep M11 — settings parity (design/haptics-sweep-2026-08-03.md). Closes R17, R18, R19.

What was wrong

Turn Forward controllers off and four rows below it stop meaning anything — nothing is forwarded, so there is no pad type to pick and no guide button to route.

Three of the six surfaces already said so. GTK desensitises (set_sensitive), the touch settings on both mobile clients pass enabled =, and the console UI dims and refuses the step (with tests). The Windows client and both controller-navigable screens left every row fully live, so you could sit there changing settings that did nothing.

R17 — dimming

Windows.enabled(s.gamepad_forwarding) on the forwarded-controller picker, pad type, guide button and hold-Select rows. Same builder the echo-cancellation row already used to follow the mic switch, so it re-renders with the toggle live.

Apple's gamepad settings had no way to express it: Row carried adjustable (which only hides the chevrons) and nothing else. Added Row.enabled, dimming the row contents only so the glass still reads as a row you can sit on — which you can, because its detail line is how you learn what to turn on first. Inertness is enforced centrally in adjust(id:) / activate(id:) rather than per builder closure. The hint bar drops "Adjust"/"Change" on a dimmed row.

Android's gamepad settings already had GpRow.enabled, documented as "dimmed + inert" — but it only faded the label. Every dimmed row still stepped and still wrote its setting; the "No profiles yet" placeholder merely looked inert because its own closures were empty. Made it real in one named place (liveRow) covering all three input paths — pad left/right, A, and a tap on the already-focused row.

R18 — the missing toggle

The DualSense / DualShock passthrough row, which the touch settings have carried beside its SC2 twin all along. It was absent exactly where it matters most: a TV box has no touch interface to fall back to, so there was no way to reach the setting at all.

R19 — Apple capture stopped taking things it doesn't use

With forwarding off, opening a slot still claimed every element's system gesture and powered the controller's IMU. Neither reaches the host: the first only took the user's screenshot/Home gestures away for nothing, the second drained the pad's battery streaming gyro over Bluetooth (which is why closeSlot is careful to power it back down).

Narrowed, not skipped. The escape chord is read off the same slot, and on tvOS it is the only controller way out of a stream — so the chord's own four buttons keep their claim. Leave Menu's gesture attached on tvOS and the press is the system's; the chord never completes and the session has no controller exit. A test pins the alias list against the chord mask, because that drift is invisible until someone is stuck in a stream with a pad in their hands.

Two corrections to R17 as filed

The finding says "Windows and Apple don't dim; every other surface does." Checking each surface:

  • Apple's touch settings were already correct (.disabled(!effective.gamepadForwarding)). The gap is the controller-navigable GamepadSettingsView.
  • Android's controller-navigable screen was also ungated — not mentioned in the finding, same defect.

Verification

Windows client cargo clippy -p punktfunk-client-windows --all-targets --locked -- -D warnings exit 0, on a real Windows box
Apple swift build clean; full suite 192 tests, 0 failures (3 new)
Android :app: + :kit: unit tests green (5 new)
Rust cargo fmt --all --check clean

Each fix was probed by reverting it in place: dropping the row gating failed 2 tests, defeating liveRow failed 1, removing the DualSense row failed 3, and breaking the Apple chord alias list failed the chord test. None are vacuous.

Not verified

On-glass. The dimming is a visual/interaction change on three UIs and R19's narrowing needs a real controller — in particular the tvOS escape chord with forwarding off is the one path worth exercising by hand, since that is what the narrowing deliberately preserves.

One deliberate non-change: "Rumble on this phone" is left ungated, matching the touch settings. Gating it here would have been a silent divergence from the reference surface.

Haptics sweep **M11 — settings parity** (`design/haptics-sweep-2026-08-03.md`). Closes **R17, R18, R19**. ## What was wrong Turn **Forward controllers** off and four rows below it stop meaning anything — nothing is forwarded, so there is no pad type to pick and no guide button to route. Three of the six surfaces already said so. GTK desensitises (`set_sensitive`), the touch settings on both mobile clients pass `enabled =`, and the console UI dims *and* refuses the step (with tests). The **Windows client** and **both controller-navigable screens** left every row fully live, so you could sit there changing settings that did nothing. ## R17 — dimming **Windows** — `.enabled(s.gamepad_forwarding)` on the forwarded-controller picker, pad type, guide button and hold-Select rows. Same builder the echo-cancellation row already used to follow the mic switch, so it re-renders with the toggle live. **Apple's gamepad settings** had no way to express it: `Row` carried `adjustable` (which only hides the chevrons) and nothing else. Added `Row.enabled`, dimming the row **contents only** so the glass still reads as a row you can sit on — which you can, because its `detail` line is how you learn what to turn on first. Inertness is enforced centrally in `adjust(id:)` / `activate(id:)` rather than per builder closure. The hint bar drops "Adjust"/"Change" on a dimmed row. **Android's gamepad settings** already had `GpRow.enabled`, documented as *"dimmed + inert"* — but it only faded the label. Every dimmed row still stepped and still wrote its setting; the "No profiles yet" placeholder merely *looked* inert because its own closures were empty. Made it real in one named place (`liveRow`) covering all three input paths — pad left/right, A, and a tap on the already-focused row. ## R18 — the missing toggle The DualSense / DualShock passthrough row, which the touch settings have carried beside its SC2 twin all along. It was absent exactly where it matters most: a TV box has no touch interface to fall back to, so there was **no way to reach the setting at all**. ## R19 — Apple capture stopped taking things it doesn't use With forwarding off, opening a slot still claimed **every** element's system gesture and powered the controller's **IMU**. Neither reaches the host: the first only took the user's screenshot/Home gestures away for nothing, the second drained the pad's battery streaming gyro over Bluetooth (which is why `closeSlot` is careful to power it back down). **Narrowed, not skipped.** The escape chord is read off the same slot, and on tvOS it is the *only* controller way out of a stream — so the chord's own four buttons keep their claim. Leave Menu's gesture attached on tvOS and the press is the system's; the chord never completes and the session has no controller exit. A test pins the alias list against the chord mask, because that drift is invisible until someone is stuck in a stream with a pad in their hands. ## Two corrections to R17 as filed The finding says *"Windows and Apple don't dim; every other surface does."* Checking each surface: - Apple's **touch** settings were already correct (`.disabled(!effective.gamepadForwarding)`). The gap is the **controller-navigable** `GamepadSettingsView`. - **Android's controller-navigable screen was also ungated** — not mentioned in the finding, same defect. ## Verification | | | |---|---| | Windows client | `cargo clippy -p punktfunk-client-windows --all-targets --locked -- -D warnings` **exit 0**, on a real Windows box | | Apple | `swift build` clean; full suite **192 tests, 0 failures** (3 new) | | Android | `:app:` + `:kit:` unit tests green (**5 new**) | | Rust | `cargo fmt --all --check` clean | Each fix was probed by reverting it in place: dropping the row gating failed 2 tests, defeating `liveRow` failed 1, removing the DualSense row failed 3, and breaking the Apple chord alias list failed the chord test. None are vacuous. ## Not verified On-glass. The dimming is a visual/interaction change on three UIs and R19's narrowing needs a real controller — in particular **the tvOS escape chord with forwarding off** is the one path worth exercising by hand, since that is what the narrowing deliberately preserves. One deliberate non-change: **"Rumble on this phone"** is left ungated, matching the touch settings. Gating it here would have been a silent divergence from the reference surface.
enricobuehler added 1 commit 2026-08-04 20:26:33 +00:00
fix(clients/settings): a controller setting you can't use no longer looks like one you can
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m14s
apple / swift (pull_request) Successful in 1m30s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 1m37s
ci / rust-arm64 (pull_request) Successful in 2m41s
ci / web (pull_request) Successful in 3m30s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m16s
android / android (pull_request) Successful in 5m37s
ci / rust (pull_request) Successful in 8m4s
9fb41affba
Turn "Forward controllers" off and four rows below it stop meaning anything — nothing is
forwarded, so there is no pad type to pick and no guide button to route. GTK desensitised
them, the touch settings on both mobile clients dimmed them and the console UI refused the
step; the Windows client and BOTH controller-navigable screens left them fully live, so you
could sit there changing settings that did nothing.

Windows: `.enabled(s.gamepad_forwarding)` on the forwarded-controller picker, pad type,
guide button and hold-Select rows — the same builder the echo-cancellation row already used
to follow the mic switch.

Apple's gamepad settings had no way to say it: `Row` carried `adjustable` (which only hides
the chevrons) and nothing else. Added `Row.enabled`, dimmed the row CONTENTS only so the
glass still reads as a focusable row, and enforced the inertness centrally in `adjust(id:)`
/ `activate(id:)` rather than in each builder's closure. The hint bar drops "Adjust"/"Change"
on a dimmed row, because advertising them was the same lie the live row told.

Android's gamepad settings already had `GpRow.enabled` — documented as "dimmed + inert" —
but it only faded the label: every dimmed row still stepped and still wrote its setting. The
"No profiles yet" placeholder looked inert only because its own closures were empty. Made it
real in one named place (`liveRow`), covering all three input paths (left/right, A, and a tap
on the already-focused row), then gated the pad rows on it.

Also on that screen: the DualSense / DualShock passthrough toggle, which the touch settings
have carried beside its SC2 twin all along. It was missing exactly where it matters most —
a TV box has no touch interface to fall back to, so there was no way to reach it at all.

Apple capture, separately: with forwarding off, opening a slot still claimed EVERY element's
system gesture and powered the controller's IMU. Neither reaches the host, so the first only
took the user's screenshot/Home gestures away for nothing and the second drained the pad's
battery streaming gyro over Bluetooth. Narrowed rather than skipped — the escape chord is
read off the same slot and on tvOS is the ONLY controller way out of a stream, so the chord's
own four buttons keep their claim. A test pins the alias list against the chord mask; if they
drift the symptom is a session nobody can leave, with nothing logged.

Closes R17, R18, R19 (design/haptics-sweep-2026-08-03.md M11). R17 as filed named Windows and
"Apple"; Apple's TOUCH settings were already correct and Android's controller-navigable screen
was not — both corrected here.

Verified: Windows clippy -D warnings exit 0 on a real Windows box; Apple swift build clean +
full suite 192 tests / 0 failures (3 new); Android :app: + :kit: green (5 new); cargo fmt
--all --check clean. Each fix probed by reverting it — every probe failed the tests it should.
enricobuehler marked the pull request as ready for review 2026-08-04 21:08:06 +00:00
enricobuehler merged commit 2f071a9a93 into main 2026-08-04 21:08:20 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#50