Skip to content

Commit dc28342

Browse files
committed
Fix user controls profile creation, selected device clarity, and controls layout alignment - PR_26163_051-user-controls-profile-and-layout-alignment
1 parent 55e066a commit dc28342

43 files changed

Lines changed: 568 additions & 250 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

account/user-controls-page.js

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -361,15 +361,24 @@ export class AccountUserControlsPage {
361361
}
362362

363363
createProfile(device) {
364+
const knownProfiles = new Map();
365+
this.readProfiles().forEach((profile) => {
366+
knownProfiles.set(profile.id, profile);
367+
});
368+
this.profiles.forEach((profile) => {
369+
knownProfiles.set(profile.id, profile);
370+
});
371+
this.profiles = [...knownProfiles.values()];
364372
const profile = this.uniqueProfileForDevice(device);
365373
if (!this.saveProfiles([profile, ...this.profiles])) {
366374
this.setStatus("FAIL: User Controls could not reach the shared DB adapter.");
367375
return;
368376
}
369-
this.editingProfile = null;
377+
const createdProfile = this.profiles.find((candidate) => candidate.id === profile.id) || profile;
378+
this.editingProfile = { id: createdProfile.id, values: createdProfile };
370379
this.viewingDefaultFamily = "";
371380
this.renderProfiles();
372-
this.setStatus(`PASS: Created ${profile.mappingProfile}. Use Edit to change it.`);
381+
this.setStatus(`PASS: Created ${createdProfile.mappingProfile}. Editing the new profile.`);
373382
}
374383

375384
renderDeviceSelect() {
@@ -418,41 +427,69 @@ export class AccountUserControlsPage {
418427
}
419428

420429
selectedInputDeviceChoices() {
421-
const fixedDevices = [
422-
{
423-
controllerId: "generic-keyboard",
424-
deviceType: "Keyboard",
425-
label: "Keyboard",
430+
const choices = [];
431+
const seenKeys = new Set();
432+
const profilesByFamily = new Map([
433+
["Keyboard", []],
434+
["Mouse", []],
435+
["Gamepad", []],
436+
]);
437+
this.profiles.forEach((profile) => {
438+
profilesByFamily.get(this.profileListFamily(profile))?.push(profile);
439+
});
440+
const addChoice = (choice) => {
441+
if (seenKeys.has(choice.selectionKey)) {
442+
return;
443+
}
444+
seenKeys.add(choice.selectionKey);
445+
choices.push(choice);
446+
};
447+
const addProfileChoice = (profile) => {
448+
addChoice({
449+
controllerId: profile.controllerId,
450+
deviceType: this.profileListFamily(profile),
451+
label: `${profile.controllerName} (${profile.mappingProfile})`,
452+
profileId: profile.id,
453+
selectionKey: `profile:${profile.id}`,
454+
selectionType: "profile",
455+
});
456+
};
457+
["Keyboard", "Mouse"].forEach((family) => {
458+
const profiles = profilesByFamily.get(family) || [];
459+
if (profiles.length) {
460+
profiles.forEach(addProfileChoice);
461+
return;
462+
}
463+
const device = this.familyDevice(family);
464+
addChoice({
465+
controllerId: device.controllerId,
466+
deviceType: family,
467+
label: device.label,
426468
profileId: "",
427-
selectionKey: "device:keyboard",
469+
selectionKey: `device:${device.value}`,
428470
selectionType: "device",
429-
},
430-
{
431-
controllerId: "generic-mouse",
432-
deviceType: "Mouse",
433-
label: "Mouse",
471+
});
472+
});
473+
const gamepadProfiles = profilesByFamily.get("Gamepad") || [];
474+
const profiledGamepadIds = new Set();
475+
gamepadProfiles.forEach((profile) => {
476+
profiledGamepadIds.add(profile.controllerId);
477+
addProfileChoice(profile);
478+
});
479+
this.deviceOptions().forEach((device) => {
480+
if (profiledGamepadIds.has(device.controllerId)) {
481+
return;
482+
}
483+
addChoice({
484+
controllerId: device.controllerId,
485+
deviceType: "Gamepad",
486+
label: device.label,
434487
profileId: "",
435-
selectionKey: "device:mouse",
488+
selectionKey: `device:gamepad:${keyFromText(device.controllerId || device.value)}`,
436489
selectionType: "device",
437-
},
438-
];
439-
const detectedGamepads = this.deviceOptions().map((device) => ({
440-
controllerId: device.controllerId,
441-
deviceType: "Gamepad",
442-
label: device.label,
443-
profileId: "",
444-
selectionKey: `device:gamepad:${keyFromText(device.controllerId || device.value)}`,
445-
selectionType: "device",
446-
}));
447-
const playerProfiles = this.profiles.map((profile) => ({
448-
controllerId: profile.controllerId,
449-
deviceType: this.profileListFamily(profile),
450-
label: `${profile.controllerName} (${profile.mappingProfile})`,
451-
profileId: profile.id,
452-
selectionKey: `profile:${profile.id}`,
453-
selectionType: "profile",
454-
}));
455-
return [...fixedDevices, ...detectedGamepads, ...playerProfiles];
490+
});
491+
});
492+
return choices;
456493
}
457494

458495
selectedInputDeviceConnected(selection) {

account/user-controls.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ <h1>User Controls</h1>
2121
</div>
2222
</section>
2323
<section class="section">
24-
<div class="container account-panel">
24+
<div class="container account-panel account-panel--fill">
2525
<div data-partial="account-side-nav" data-partial-replace></div>
26-
<div class="card">
27-
<div class="card-body content-stack">
26+
<div class="card card--fill">
27+
<div class="card-body card-body--fill content-stack">
2828
<div>
2929
<div class="kicker">User Controls</div>
3030
<h2>Physical Input Mapping</h2>

assets/theme-v2/css/accordion.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ summary::-webkit-details-marker {
2222
padding: var(--space-14)
2323
}
2424

25+
.accordion-stack--left-right {
26+
align-items: start;
27+
grid-template-columns: repeat(2, minmax(var(--space-0), 1fr))
28+
}
29+
2530
details.vertical-accordion {
2631
border: var(--border-standard);
2732
border-radius: var(--radius-lg);
@@ -306,3 +311,9 @@ body:not(.tool-focus-mode) .tool-center-panel>.accordion-fill-stack>.accordion-f
306311
min-height: var(--space-0);
307312
overflow: auto
308313
}
314+
315+
@media(max-width:620px) {
316+
.accordion-stack--left-right {
317+
grid-template-columns: var(--one-column)
318+
}
319+
}

assets/theme-v2/css/layout.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,15 @@ body.tool-focus-mode .tool-workspace--table-scroll-focus .tool-table-scroll-regi
376376
gap: var(--space-18)
377377
}
378378

379+
.account-panel--fill {
380+
align-items: stretch;
381+
min-height: 100%
382+
}
383+
384+
.account-panel--fill>.card {
385+
width: 100%
386+
}
387+
379388
.admin-page-stack {
380389
display: grid;
381390
gap: var(--space-18);

assets/theme-v2/css/panels.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@
3838
padding: var(--space-18)
3939
}
4040

41+
.card--fill {
42+
display: flex;
43+
flex-direction: column;
44+
min-height: 100%;
45+
width: 100%
46+
}
47+
48+
.card-body--fill {
49+
flex: 1 1 auto;
50+
min-height: 100%;
51+
width: 100%
52+
}
53+
4154
.tool-workspace article.card>.card-body.content-stack {
4255
margin: var(--space-8)
4356
}
Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
<aside class="side-menu" aria-label="Account pages" data-account-side-nav>
2-
<a data-account-side-nav-link data-route="account" href="/account/index.html">Account Home</a>
3-
<a data-account-side-nav-link data-route="account-achievements" href="/account/achievements.html">Achievements</a>
4-
<a data-account-side-nav-link data-route="account-preferences" href="/account/preferences.html">Preferences</a>
5-
<a data-account-side-nav-link data-route="account-profile" href="/account/profile.html">Profile</a>
6-
<a data-account-side-nav-link data-route="account-security" href="/account/security.html">Security</a>
7-
<a data-account-side-nav-link data-route="account-user-controls" href="/account/user-controls.html">User Controls</a>
2+
<div class="accordion-stack accordion-stack--left-right" data-account-side-nav-accordion-layout="left-right">
3+
<details class="vertical-accordion" open data-account-side-nav-accordion="left">
4+
<summary>Account Pages</summary>
5+
<div class="accordion-body content-stack content-stack--compact">
6+
<a data-account-side-nav-link data-route="account" href="/account/index.html">Account Home</a>
7+
<a data-account-side-nav-link data-route="account-achievements" href="/account/achievements.html">Achievements</a>
8+
<a data-account-side-nav-link data-route="account-preferences" href="/account/preferences.html">Preferences</a>
9+
<a data-account-side-nav-link data-route="account-profile" href="/account/profile.html">Profile</a>
10+
<a data-account-side-nav-link data-route="account-security" href="/account/security.html">Security</a>
11+
<a data-account-side-nav-link data-route="account-user-controls" href="/account/user-controls.html">User Controls</a>
12+
</div>
13+
</details>
14+
<details class="vertical-accordion" data-account-side-nav-accordion="right">
15+
<summary>Account Guidance</summary>
16+
<div class="accordion-body content-stack content-stack--compact">
17+
<p class="status">Manage profile, security, preferences, achievements, and controls from Account pages.</p>
18+
</div>
19+
</details>
20+
</div>
821
</aside>
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# PR_26163_051-user-controls-profile-and-layout-alignment
2+
3+
## Branch Validation
4+
5+
- PASS: Current branch is `main`.
6+
- Expected branch: `main`.
7+
8+
## Impacted Lane
9+
10+
- Account/User Controls runtime lane.
11+
- Toolbox Controls launch/render lane.
12+
- Theme V2 account side-nav/layout lane.
13+
- Workspace contract lane through legacy command `npm run test:workspace-v2`.
14+
15+
## Requirement Checklist
16+
17+
- PASS: Read `docs_build/dev/PROJECT_INSTRUCTIONS.md` before changes.
18+
- PASS: Verified current branch is `main` before changes.
19+
- PASS: `Create my profile` creates a brand-new profile and opens that new profile in edit mode.
20+
- Evidence: Playwright creates `Keyboard Profile`, verifies the edit row opens immediately, saves it, then creates `Keyboard 2 Profile` and verifies the controller name input is `Keyboard 2`.
21+
- PASS: `Create my profile` does not open an existing profile.
22+
- Evidence: Playwright verifies the first saved Keyboard row returns, then the second Create action opens the new `Keyboard 2` profile instead of the existing `Keyboard Profile`.
23+
- PASS: Selected Device is simplified and no longer lists duplicated raw devices plus saved profiles.
24+
- Evidence: Playwright verifies saved `Keyboard`, `Mouse`, and gamepad profiles replace their raw device choices, while an unprofiled detected controller remains selectable.
25+
- PASS: `toolbox/controls` is aligned with the updated side accordion pattern where applicable.
26+
- Evidence: Toolbox Controls keeps its left/right accordion columns and now exposes `data-controls-side-accordion="left"` and `data-controls-side-accordion="right"`; Playwright verifies both render.
27+
- PASS: `assets/theme-v2/partials/account-side-nav.html` has left/right accordion structure.
28+
- Evidence: Account side nav now exposes `data-account-side-nav-accordion-layout="left-right"` plus independently toggled left and right `details.vertical-accordion` sections; Playwright verifies both open/close.
29+
- PASS: `account/user-controls.html` Account/User Controls card and body use 100% available space.
30+
- Evidence: Reusable Theme V2 fill classes are applied and verified by Playwright on `.account-panel--fill`, `.card--fill`, and `.card-body--fill`.
31+
- PASS: Scope remained limited to account controls, toolbox controls alignment, Theme V2 partial/layout files, tests, and reports.
32+
33+
## Changed Files
34+
35+
- `account/user-controls-page.js`
36+
- `account/user-controls.html`
37+
- `assets/theme-v2/css/layout.css`
38+
- `assets/theme-v2/css/panels.css`
39+
- `assets/theme-v2/partials/account-side-nav.html`
40+
- `toolbox/controls/index.html`
41+
- `tests/playwright/tools/InputMappingV2Tool.spec.mjs`
42+
- `tests/playwright/account/AchievementsPage.spec.mjs`
43+
- `docs_build/dev/reports/playwright_v8_coverage_report.txt`
44+
- `docs_build/dev/reports/coverage_changed_js_guardrail.txt`
45+
- `docs_build/dev/reports/dependency_gating_report.md`
46+
- `docs_build/dev/reports/dependency_hydration_reuse_report.md`
47+
- `docs_build/dev/reports/execution_graph_reuse_report.md`
48+
- `docs_build/dev/reports/failure_fingerprint_report.md`
49+
- `docs_build/dev/reports/filesystem_scan_reduction_report.md`
50+
- `docs_build/dev/reports/incremental_validation_report.md`
51+
- `docs_build/dev/reports/lane_compilation_report.md`
52+
- `docs_build/dev/reports/lane_deduplication_report.md`
53+
- `docs_build/dev/reports/lane_input_validation_report.md`
54+
- `docs_build/dev/reports/lane_manifests/workspace-contract.json`
55+
- `docs_build/dev/reports/lane_runtime_optimization_report.md`
56+
- `docs_build/dev/reports/lane_snapshot_report.md`
57+
- `docs_build/dev/reports/lane_snapshots/workspace-contract.json`
58+
- `docs_build/dev/reports/lane_warm_start_report.md`
59+
- `docs_build/dev/reports/lane_warm_starts/workspace-contract.json`
60+
- `docs_build/dev/reports/monolith_trigger_removal_report.md`
61+
- `docs_build/dev/reports/persistent_lane_manifest_report.md`
62+
- `docs_build/dev/reports/playwright_discovery_ownership_report.md`
63+
- `docs_build/dev/reports/playwright_discovery_scope_report.md`
64+
- `docs_build/dev/reports/playwright_structure_audit.md`
65+
- `docs_build/dev/reports/retry_suppression_report.md`
66+
- `docs_build/dev/reports/slow_path_pruning_report.md`
67+
- `docs_build/dev/reports/static_validation_report.md`
68+
- `docs_build/dev/reports/targeted_file_manifest_report.md`
69+
- `docs_build/dev/reports/test_cleanup_performance_report.md`
70+
- `docs_build/dev/reports/test_cleanup_routing_report.md`
71+
- `docs_build/dev/reports/testing_lane_execution_report.md`
72+
- `docs_build/dev/reports/validation_cache_report.md`
73+
- `docs_build/dev/reports/zero_browser_preflight_report.md`
74+
- Required review artifacts are generated separately:
75+
- `docs_build/dev/reports/codex_review.diff`
76+
- `docs_build/dev/reports/codex_changed_files.txt`
77+
78+
## Validation Performed
79+
80+
- PASS: `node --check account/user-controls-page.js`
81+
- PASS: `node --check tests/playwright/tools/InputMappingV2Tool.spec.mjs`
82+
- PASS: `node --check tests/playwright/account/AchievementsPage.spec.mjs`
83+
- PASS: HTML restriction scan:
84+
- `rg --pcre2 -n "<script(?![^>]+src=)|<style|\son[a-z]+\s*=|style\s*=" account/user-controls.html toolbox/controls/index.html assets/theme-v2/partials/account-side-nav.html`
85+
- Result: no inline script/style/handler matches.
86+
- PASS: Targeted Playwright:
87+
- `npx playwright test tests/playwright/tools/InputMappingV2Tool.spec.mjs tests/playwright/account/AchievementsPage.spec.mjs`
88+
- Result: 9 passed.
89+
- PASS: Requested workspace command:
90+
- `npm run test:workspace-v2`
91+
- Result: 5 passed.
92+
- Note: command name is legacy; this routes the current workspace-contract validation lane.
93+
94+
## Playwright Result
95+
96+
- PASS: Targeted Controls/User Controls and shared Account side-nav coverage passed.
97+
- PASS: `npm run test:workspace-v2` passed.
98+
99+
## Coverage
100+
101+
- PASS: `docs_build/dev/reports/playwright_v8_coverage_report.txt` produced.
102+
- PASS: Changed runtime JS was exercised:
103+
- `(93%) account/user-controls-page.js`
104+
- `(94%) toolbox/controls/controls.js`
105+
- PASS: Coverage report states no low-coverage changed runtime JS warnings.
106+
- WARN: Changed Playwright spec files are not browser runtime JavaScript and are not collected as V8 runtime coverage; this is expected.
107+
108+
## Skipped Lanes
109+
110+
- SKIP: Full samples smoke test.
111+
- Reason: explicitly requested not to run full samples smoke; no sample JSON or sample runtime files changed.
112+
- SKIP: Engine input lane.
113+
- Reason: no `src/engine/input` implementation changed in this PR.
114+
- SKIP: Full repo Playwright suite.
115+
- Reason: targeted Account/User Controls, Toolbox Controls, shared account side-nav, and requested workspace-contract lane covered the affected surfaces.
116+
117+
## Manual Validation Steps
118+
119+
1. Open `/account/user-controls.html` as a user session.
120+
2. Click Keyboard `Create my profile`; verify the newly created profile opens in edit mode.
121+
3. Save it, then click Keyboard `Create my profile` again; verify the new edit row is `Keyboard 2`, not the existing Keyboard profile.
122+
4. Create Mouse and detected game controller profiles; verify Selected Device shows saved profile choices without duplicate raw device entries for those same devices.
123+
5. Open and close the Account side-nav left and right accordion sections.
124+
6. Open `/toolbox/controls/index.html`; verify Game Controls renders with left and right side accordions and expected default rows.
125+
126+
## Samples Decision
127+
128+
- SKIP: Full samples smoke test was intentionally not run because the PR does not touch samples and the user explicitly requested not to run it.

0 commit comments

Comments
 (0)