Skip to content

Commit 3af05e2

Browse files
committed
Group user control profile selection by device type - PR_26163_067-user-controls-profile-radio-groups
1 parent e67e6d8 commit 3af05e2

7 files changed

Lines changed: 831 additions & 291 deletions

File tree

account/user-controls-page.js

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export class AccountUserControlsPage {
139139
this.captureService = new InputCaptureService({ inputService: this.inputService });
140140
this.profiles = [];
141141
this.editingProfile = null;
142-
this.selectedInputDevice = null;
142+
this.selectedInputDevices = new Map();
143143
this.sessionUserKey = "";
144144
this.viewingDefaultFamily = "";
145145
this.devicePollingTimer = null;
@@ -162,7 +162,7 @@ export class AccountUserControlsPage {
162162
this.inputService.attach();
163163
this.sessionUserKey = this.currentSessionUserKey();
164164
this.profiles = this.readProfiles();
165-
this.selectedInputDevice = this.readSelectedInputDevice();
165+
this.selectedInputDevices = this.readSelectedInputDevices();
166166
this.renderDeviceSelect();
167167
this.renderDefaultFallbacks();
168168
this.renderTypes();
@@ -215,7 +215,7 @@ export class AccountUserControlsPage {
215215
this.repository = createControlsToolApiRepository();
216216
this.sessionUserKey = this.currentSessionUserKey();
217217
this.profiles = this.readProfiles();
218-
this.selectedInputDevice = this.readSelectedInputDevice();
218+
this.selectedInputDevices = this.readSelectedInputDevices();
219219
this.editingProfile = null;
220220
this.viewingDefaultFamily = "";
221221
this.renderDeviceSelect();
@@ -317,16 +317,46 @@ export class AccountUserControlsPage {
317317
return Array.isArray(result) ? result.map((profile) => this.normalizeProfile(profile)) : [];
318318
}
319319

320-
readSelectedInputDevice() {
320+
selectionFamily(selection = {}) {
321+
const deviceType = normalizeText(selection.deviceType);
322+
if (deviceType === "Keyboard" || deviceType === "Mouse") {
323+
return deviceType;
324+
}
325+
return "Gamepad";
326+
}
327+
328+
selectedInputDeviceForFamily(family) {
329+
return this.selectedInputDevices.get(this.selectionFamily({ deviceType: family })) || null;
330+
}
331+
332+
selectedInputDeviceMapFromList(selections = []) {
333+
const selectedInputDevices = new Map();
334+
(Array.isArray(selections) ? selections : []).forEach((selection) => {
335+
if (selection?.selectionKey) {
336+
selectedInputDevices.set(this.selectionFamily(selection), selection);
337+
}
338+
});
339+
return selectedInputDevices;
340+
}
341+
342+
readSelectedInputDevices() {
321343
if (!this.currentSessionUserKey()) {
322-
return null;
344+
return new Map();
345+
}
346+
let result = this.repository.listSelectedInputDevices();
347+
if (result?.error) {
348+
this.repository = createControlsToolApiRepository();
349+
result = this.repository.listSelectedInputDevices();
323350
}
324-
let result = this.repository.getSelectedInputDevice();
351+
if (Array.isArray(result)) {
352+
return this.selectedInputDeviceMapFromList(result);
353+
}
354+
result = this.repository.getSelectedInputDevice();
325355
if (result?.error) {
326356
this.repository = createControlsToolApiRepository();
327357
result = this.repository.getSelectedInputDevice();
328358
}
329-
return result && !result.error ? result : null;
359+
return result && !result.error ? this.selectedInputDeviceMapFromList([result]) : new Map();
330360
}
331361

332362
saveSelectedInputDevice(selection) {
@@ -339,8 +369,13 @@ export class AccountUserControlsPage {
339369
this.repository = createControlsToolApiRepository();
340370
result = this.repository.saveSelectedInputDevice(selection);
341371
}
372+
if (Array.isArray(result?.selectedInputDevices)) {
373+
this.selectedInputDevices = this.selectedInputDeviceMapFromList(result.selectedInputDevices);
374+
this.renderSelectedInputDevices();
375+
return true;
376+
}
342377
if (result?.selectedInputDevice) {
343-
this.selectedInputDevice = result.selectedInputDevice;
378+
this.selectedInputDevices.set(this.selectionFamily(result.selectedInputDevice), result.selectedInputDevice);
344379
this.renderSelectedInputDevices();
345380
return true;
346381
}
@@ -552,14 +587,21 @@ export class AccountUserControlsPage {
552587
if (!this.elements.selectedDeviceStatus) {
553588
return;
554589
}
555-
if (!this.selectedInputDevice?.selectionKey) {
590+
const selections = ["Keyboard", "Mouse", "Gamepad"]
591+
.map((family) => this.selectedInputDeviceForFamily(family))
592+
.filter(Boolean);
593+
if (!selections.length) {
556594
this.elements.selectedDeviceStatus.textContent = "Default Profile";
557595
return;
558596
}
559-
const connected = this.selectedInputDeviceConnected(this.selectedInputDevice);
560-
this.elements.selectedDeviceStatus.textContent = connected
561-
? `Selected Device: ${this.selectedInputDevice.label || this.selectedInputDevice.deviceType}.`
562-
: "Selected device not connected. Using Default Profile.";
597+
const labels = selections.map((selection) => (
598+
this.selectedInputDeviceConnected(selection)
599+
? selection.label || selection.deviceType
600+
: `${selection.deviceType} selected device not connected. Using Default Profile`
601+
));
602+
this.elements.selectedDeviceStatus.textContent = labels.length === 1
603+
? `Selected Device: ${labels[0]}.`
604+
: `Selected Devices: ${labels.join("; ")}.`;
563605
}
564606

565607
renderSelectedInputDevices() {
@@ -742,11 +784,12 @@ export class AccountUserControlsPage {
742784
selectedDeviceCell(choice) {
743785
const cell = document.createElement("td");
744786
const input = document.createElement("input");
787+
const family = this.selectionFamily(choice);
745788
input.type = "radio";
746-
input.name = "account-user-controls-selected-device";
789+
input.name = `account-user-controls-selected-device-${family.toLowerCase()}`;
747790
input.value = choice.selectionKey;
748791
input.dataset.accountUserControlsSelectedDevice = choice.selectionKey;
749-
input.checked = this.selectedInputDevice?.selectionKey === choice.selectionKey;
792+
input.checked = this.selectedInputDeviceForFamily(family)?.selectionKey === choice.selectionKey;
750793
input.setAttribute("aria-label", `Select ${choice.label}`);
751794
cell.append(input);
752795
return cell;
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# PR_26163_067-user-controls-profile-radio-groups
2+
3+
## Branch Validation
4+
- PASS: Current branch is `main`.
5+
- Expected branch: `main`.
6+
7+
## Summary
8+
- Updated Account > User Controls selected profile radios so Keyboard, Mouse, and Gamepad selections each use an independent radio group.
9+
- Persisted active profile selection by device type through the shared mock DB adapter.
10+
- Kept detected controller rows profile-free; controller detection continues to feed the dropdown workflow only.
11+
12+
## Requirement Checklist
13+
- PASS: Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` before edits.
14+
- PASS: Verified current branch is `main` before changes.
15+
- PASS: Keyboard profiles use radio group `account-user-controls-selected-device-keyboard`.
16+
- PASS: Mouse profiles use radio group `account-user-controls-selected-device-mouse`.
17+
- PASS: Gamepad profiles use radio group `account-user-controls-selected-device-gamepad`.
18+
- PASS: Selecting a Gamepad profile does not deselect the active Keyboard profile.
19+
- PASS: Selecting a Mouse profile does not deselect the active Gamepad profile.
20+
- PASS: One active profile is persisted per device type for Keyboard, Mouse, and Gamepad.
21+
- PASS: Default profiles participate in their device-type groups.
22+
- PASS: User-created profiles participate in their device-type groups.
23+
- PASS: Detection rows remain profile-free and contain no radios.
24+
- PASS: Active profile selection persists by device type for the current user.
25+
- PASS: Scope stayed limited to Account/User Controls profile selection behavior, mock DB selection persistence, targeted tests, and required reports.
26+
27+
## Changed Files
28+
- `account/user-controls-page.js`
29+
- `src/dev-runtime/persistence/tool-repositories/input-mapping-mock-repository.js`
30+
- `tests/playwright/tools/InputMappingV2Tool.spec.mjs`
31+
- `docs_build/dev/reports/playwright_v8_coverage_report.txt`
32+
- `docs_build/dev/reports/PR_26163_067-user-controls-profile-radio-groups.md`
33+
- `docs_build/dev/reports/codex_changed_files.txt`
34+
- `docs_build/dev/reports/codex_review.diff`
35+
36+
## Impacted Lane
37+
- Account/User Controls profile selection.
38+
- Shared mock DB adapter persistence for selected input device/profile records.
39+
- Targeted Account/User Controls Playwright coverage.
40+
41+
## Validation Performed
42+
- PASS: `node --check account/user-controls-page.js`
43+
- PASS: `node --check src/dev-runtime/persistence/tool-repositories/input-mapping-mock-repository.js`
44+
- PASS: `node --check tests/playwright/tools/InputMappingV2Tool.spec.mjs`
45+
- PASS: `rg -n '<style| on[a-z]+=' account/user-controls.html account/user-controls-page.js src/dev-runtime/persistence/tool-repositories/input-mapping-mock-repository.js tests/playwright/tools/InputMappingV2Tool.spec.mjs` returned no matches.
46+
- PASS: `git diff --check -- account/user-controls-page.js src/dev-runtime/persistence/tool-repositories/input-mapping-mock-repository.js tests/playwright/tools/InputMappingV2Tool.spec.mjs`
47+
- PASS: `npx playwright test tests/playwright/tools/InputMappingV2Tool.spec.mjs --grep "User Controls owns physical input mapping accordions and profiles|User Controls creates one profile from only the selected controller dropdown device" --reporter=line`
48+
- Result: 2 passed.
49+
- PASS: `npm run test:workspace-v2`
50+
- Result: 5 passed in the workspace-contract lane.
51+
52+
## Playwright Result
53+
- PASS: Targeted Account/User Controls Playwright checks passed.
54+
- PASS: Required `npm run test:workspace-v2` passed.
55+
56+
## V8 Coverage
57+
- PASS: `account/user-controls-page.js` collected browser V8 coverage at 92%.
58+
- WARN: `src/dev-runtime/persistence/tool-repositories/input-mapping-mock-repository.js` is exercised through the local dev-runtime/server path, but Chromium browser V8 coverage does not collect that server-side module.
59+
- Report: `docs_build/dev/reports/playwright_v8_coverage_report.txt`.
60+
61+
## Manual Validation Steps
62+
1. Open `account/user-controls.html` as a signed-in user.
63+
2. Select Keyboard Default Profile, Mouse Default Profile, and Gamepad Default Profile; confirm all three stay selected and the status lists all selected devices.
64+
3. Create and save Keyboard, Mouse, and Gamepad user profiles.
65+
4. Select one user-created profile in each device type; confirm selecting one device type does not clear the other device type selections.
66+
5. Refresh detected controllers; confirm detected controllers appear in the controller dropdown and no detected row radios are rendered.
67+
6. Reload the page; confirm active Keyboard, Mouse, and Gamepad selections persist for the current user.
68+
69+
## Skipped Lanes
70+
- SKIP: Full samples smoke test. Safe to skip because this PR only changes Account/User Controls profile selection and mock DB persistence, and the user explicitly requested no full samples smoke test.
71+
- SKIP: Unrelated Toolbox/Game Controls lanes. Safe to skip because no Toolbox/Game Controls runtime behavior changed.
72+
73+
## Samples Validation
74+
- SKIP: Full samples smoke test was not run by request.
75+
76+
## Packaging
77+
- PASS: Repo-structured delta ZIP produced under `tmp/PR_26163_067-user-controls-profile-radio-groups_delta.zip`.
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
account/user-controls.html
2-
assets/theme-v2/css/layout.css
3-
assets/theme-v2/css/typography.css
1+
account/user-controls-page.js
2+
src/dev-runtime/persistence/tool-repositories/input-mapping-mock-repository.js
43
tests/playwright/tools/InputMappingV2Tool.spec.mjs
5-
docs_build/dev/reports/PR_26163_066-user-controls-layout-polish.md
4+
docs_build/dev/reports/playwright_v8_coverage_report.txt
5+
docs_build/dev/reports/PR_26163_067-user-controls-profile-radio-groups.md
66
docs_build/dev/reports/codex_review.diff
77
docs_build/dev/reports/codex_changed_files.txt
8+
tmp/PR_26163_067-user-controls-profile-radio-groups_delta.zip

0 commit comments

Comments
 (0)