Skip to content

Commit 4685fdf

Browse files
committed
Polish Input Mapping V2 nav layout and labels - PR_26140_098-polish-input-mapping-v2-nav-layout-and-labels
1 parent 4ea50bd commit 4685fdf

8 files changed

Lines changed: 96 additions & 58 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Input Mapping V2 Nav Layout Labels Report
2+
3+
## Scope
4+
- PR: PR_26140_098-polish-input-mapping-v2-nav-layout-and-labels
5+
- Source of truth: user PR_098 request. `docs/pr/BUILD_PR.md` currently points at an unrelated Level 18 overlay runtime hardening rebase, so this workflow used the explicit PR_098 request after reading `docs/dev/PROJECT_INSTRUCTIONS.md`.
6+
7+
## Changes Applied
8+
- Restored the standalone Input Mapping V2 tool nav as `inputMappingV2ToolNav` with `Import`, `Copy JSON`, and `JSON` actions.
9+
- Kept the workspace nav path intact and kept the tool nav hidden during workspace launch.
10+
- Moved `Gestures` and `Capture` into the center column above `Captured Mappings`.
11+
- Moved `Status / Log` to the bottom of the right column and renamed the JSON preview accordion from `Export` to `JSON`.
12+
- Changed Actions button labels to `Add`, `Delete`, `Delete` and rewired the old clear button path to delete only the selected action tile instead of clearing inputs.
13+
- Updated game controller capture button labels to the compact two-line format: `Capture Game` plus the vendor/product line when available.
14+
- Removed the visible Sample 0104 diagnostics card from Gamepad Diagnostics, leaving browser and Input Mapping V2 engine diagnostics only.
15+
- Updated Input Mapping V2 help text to refer to `Copy JSON` and `JSON`.
16+
- Updated focused Playwright coverage for nav labels, layout order, diagnostics visibility, compact gamepad capture labels, action labels, and non-bulk delete behavior.
17+
18+
## Contracts And Constraints
19+
- No schema changes.
20+
- No sample JSON changes.
21+
- No engine core changes.
22+
- External JS/CSS-only constraint preserved; changed HTML has no inline script/style/event-handler matches.
23+
- Captured mapping tile, gamepad auto-poll, combo, drag, and wheel behavior are preserved by existing coverage.
24+
- Sample 0104 comparison was removed from visible diagnostics because it duplicated the same InputService-backed state now shown by the Input Mapping V2 engine diagnostics. It can remain a conceptual comparison point in reports, but it is no longer useful as visible Gamepad Diagnostics UI.
25+
26+
## Validation
27+
- `node --check tools/input-mapping-v2/js/bootstrap.js`: PASS
28+
- `node --check tools/input-mapping-v2/js/ToolStarterApp.js`: PASS
29+
- `node --check tools/input-mapping-v2/js/controls/ExportControl.js`: PASS
30+
- `node --check tools/input-mapping-v2/js/services/EngineInputSourceService.js`: PASS
31+
- `npx playwright test tests/playwright/tools/WorkspaceManagerV2.spec.mjs --project=playwright --workers=1 --reporter=list --grep "Input Mapping V2"`: PASS, 2 tests
32+
- `rg --pcre2 -n "<script(?![^>]*src=)|<style| on[a-zA-Z]+=" tools/input-mapping-v2/index.html tools/input-mapping-v2/how_to_use.html`: PASS, no matches
33+
- `npm run test:workspace-v2`: PASS, 61 tests
34+
- `git diff --check`: PASS, line-ending warnings only
35+
- Full samples smoke test: not run, per request
36+
- Delta ZIP verification: PASS, 12 files, nonzero size
37+
38+
## Notes
39+
- `Import` is wired to an actionable warning in standalone mode because PR_098 restores the nav action but does not introduce an Input Mapping V2 file-import contract or schema changes. Workspace launch data remains the supported import path for this surface.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,8 +1430,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14301430
buttons.map((button) => button.textContent.split("\n").map((line) => line.trim()).filter(Boolean))
14311431
));
14321432
expect(gamepadCaptureLabels).toEqual([
1433-
["Capture (Gamepad 0)", "Vendor: 081f Product: e401", "USB gamepad"],
1434-
["Capture Logitech RumblePad 2", "Vendor: 046d Product: c218", "Logitech RumblePad 2"]
1433+
["Capture Game", "Vendor: 081f Product: e401"],
1434+
["Capture Game", "Vendor: 046d Product: c218"]
14351435
]);
14361436
await expect(page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='0']")).not.toContainText("Product: e401)");
14371437
await expect(page.locator(".input-mapping-v2__gamepad-capture-button[data-input-mapping-gamepad-index='1']")).not.toContainText("Product: c218)");
@@ -1473,9 +1473,11 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14731473
await expect(page.locator("[data-tool-starter-summary]")).toBeVisible();
14741474
await expect(page.locator(".tools-platform-frame__title[data-tool-id='input-mapping-v2']")).toHaveText("Input Mapping V2");
14751475
await expect(page.locator(".tool-starter__tool__menu")).toBeVisible();
1476-
await expect(page.locator('[data-launch-mode-nav="tool"] button')).toHaveText(["Export", "Copy JSON"]);
1476+
await expect(page.locator('[data-launch-mode-nav="tool"] button')).toHaveText(["Import", "Copy JSON", "JSON"]);
14771477
await expect(page.locator("#toolExportToolStateButton")).toHaveCount(0);
14781478
await expect(page.locator(".tool-starter__workspace__menu")).toBeHidden();
1479+
await page.locator("#toolImportButton").click();
1480+
await expect(page.locator("#statusLog")).toHaveValue(/WARN Input Mapping V2 Import is available through workspace launch data/);
14791481
const fullscreenLayout = await page.locator(".input-mapping-v2.tool-starter.app-shell").evaluate((shell) => {
14801482
const left = shell.querySelector(".tool-starter__panel--left").getBoundingClientRect();
14811483
const center = shell.querySelector(".tool-starter__panel--center").getBoundingClientRect();
@@ -1496,9 +1498,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
14961498
expect(fullscreenLayout.rightGap).toBeLessThan(40);
14971499
expect(fullscreenLayout.centerWidth).toBeGreaterThan(1000);
14981500
expect(Math.max(...fullscreenLayout.rightAccordionHeights) - Math.min(...fullscreenLayout.rightAccordionHeights)).toBeLessThanOrEqual(3);
1499-
await expect(page.locator(".tool-starter__panel--left > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Actions", "Devices", "Gestures", "Capture"]);
1500-
await expect(page.locator(".tool-starter__panel--center > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Captured Mappings"]);
1501-
await expect(page.locator(".tool-starter__panel--right > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Diagnostics", "Status / Log", "Export"]);
1501+
await expect(page.locator(".tool-starter__panel--left > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Actions", "Devices"]);
1502+
await expect(page.locator(".tool-starter__panel--center > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Gestures", "Capture", "Captured Mappings"]);
1503+
await expect(page.locator(".tool-starter__panel--right > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Diagnostics", "JSON", "Status / Log"]);
15021504
await expect(page.locator(".input-mapping-v2__device-card strong")).toHaveText([
15031505
"Keyboard",
15041506
"Mouse",
@@ -1524,8 +1526,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15241526
expect(actionOptions).toEqual([...actionOptions].sort((left, right) => left.localeCompare(right)));
15251527
expect(actionOptions).toEqual(expect.arrayContaining(["Move Left", "Confirm", "Cancel", "Fire", "Thrust", "Rotate Left", "Rotate Right", "Pause", "Select", "Start"]));
15261528
await expect(page.locator("#inputMappingV2ResetActionsButton")).toHaveCount(0);
1527-
await expect(page.locator("#inputMappingV2ClearActionButton")).toHaveText("Clear Actions");
1528-
await expect(page.locator("#actionSetupContent .input-mapping-v2__button-row button")).toHaveText(["Add Action", "Clear Actions", "Delete Action"]);
1529+
await expect(page.locator("#inputMappingV2ClearActionButton")).toHaveText("Delete");
1530+
await expect(page.locator("#actionSetupContent .input-mapping-v2__button-row button")).toHaveText(["Add", "Delete", "Delete"]);
15291531
await expect(page.locator("#inputMappingV2RumbleFeedbackCheckbox")).toBeVisible();
15301532
await page.locator("#inputMappingV2RumbleFeedbackCheckbox").check();
15311533
await expect(page.locator("#statusLog")).toHaveValue(/WARN Gamepad rumble unavailable:/);
@@ -1569,7 +1571,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
15691571
await expect(page.locator("#inputMappingV2Diagnostics")).toContainText("Engine input capabilities");
15701572
await expect(page.locator("#inputMappingV2Diagnostics")).toContainText("Raw navigator.getGamepads()");
15711573
await expect(page.locator("#inputMappingV2Diagnostics")).toContainText("InputService gamepad state");
1572-
await expect(page.locator("#inputMappingV2Diagnostics")).toContainText("Sample 0104 engine/input path");
1574+
await expect(page.locator("#inputMappingV2Diagnostics")).not.toContainText("Sample 0104 engine/input path");
15731575
await expect(page.locator("#inputMappingV2Diagnostics")).toContainText("Browser API available");
15741576
await expect(page.locator("#inputMappingV2Diagnostics")).toContainText("navigator.getGamepads() count");
15751577
await expect(page.locator("#inputMappingV2Diagnostics")).toContainText("Last poll timestamp");
@@ -1702,8 +1704,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17021704
await page.keyboard.press("KeyB");
17031705
await expect(page.locator("#statusLog")).toHaveValue(/OK Keyboard KeyB mapped to Confirm\./);
17041706
await expect(page.locator("#captureInputContent")).not.toContainText("Keyboard KeyB mapped to Confirm.");
1705-
await page.locator("#inputMappingV2DeleteActionButton").click();
1707+
await page.locator("#inputMappingV2ClearActionButton").click();
17061708
await expect(page.locator(".input-mapping-v2__mapping-card", { hasText: "Confirm" })).toHaveCount(0);
1709+
await expect(page.locator(".input-mapping-v2__mapping-card", { hasText: "Move Left" })).toHaveCount(1);
17071710
await expect(page.locator("#inputMappingV2ActionSelect option[value='confirm']")).toHaveJSProperty("disabled", false);
17081711
const actionValues = await page.locator("#inputMappingV2ActionSelect option").evaluateAll((options) => (
17091712
options.filter((option) => !option.disabled).map((option) => option.value)
@@ -1756,8 +1759,8 @@ test.describe("Workspace Manager V2 bootstrap", () => {
17561759
await expect(page.locator("#inputMappingV2Diagnostics")).toContainText(/navigator\.getGamepads\(\) count2/);
17571760
await expect(page.locator("#inputMappingV2DeviceList")).toContainText("2 connected game controllers detected.");
17581761
await expect(page.locator(".input-mapping-v2__gamepad-capture-button")).toHaveCount(2);
1759-
await expect(page.locator(".input-mapping-v2__gamepad-capture-button").nth(0)).toContainText("Mock Flight Stick");
1760-
await expect(page.locator(".input-mapping-v2__gamepad-capture-button").nth(1)).toContainText("Logitech RumblePad 2");
1762+
await expect(page.locator(".input-mapping-v2__gamepad-capture-button").nth(0)).toContainText("Capture Game");
1763+
await expect(page.locator(".input-mapping-v2__gamepad-capture-button").nth(1)).toContainText("Vendor: 046d Product: c218");
17611764
const gamepadButtonLayout = await page.locator(".input-mapping-v2__gamepad-capture-button").evaluateAll((buttons) => buttons.map((button) => {
17621765
const box = button.getBoundingClientRect();
17631766
return {
@@ -1825,7 +1828,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
18251828
expect(asteroidsManifestInputMapping.actions.map((action) => action.label)).toEqual(actionOptions);
18261829
await page.locator("#toolExportButton").click();
18271830
await expect(page.locator("#inspectorOutput")).toContainText('"payload"');
1828-
await expect(page.locator("#statusLog")).toHaveValue(/OK Input Mapping V2 export preview written\./);
1831+
await expect(page.locator("#statusLog")).toHaveValue(/OK Input Mapping V2 JSON preview written\./);
18291832
await page.evaluate(() => {
18301833
Object.defineProperty(navigator, "clipboard", {
18311834
configurable: true,

tools/input-mapping-v2/how_to_use.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ <h1>Input Mapping V2</h1>
1919
<li>Use Devices and Gestures to choose supported input patterns.</li>
2020
<li>Use Capture Keyboard, Capture Mouse, or a connected game controller capture button.</li>
2121
<li>Click a captured input token to delete that binding.</li>
22-
<li>Use Copy JSON or Export when the mapping is ready.</li>
22+
<li>Use Copy JSON or JSON when the mapping is ready.</li>
2323
</ol>
2424
<nav class="tool-starter__menu" aria-label="Input Mapping V2 links">
2525
<a href="./index.html">Open Tool</a>

tools/input-mapping-v2/index.html

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
3737
</div>
3838
</details>
3939

40+
<nav id="inputMappingV2ToolNav" class="tool-starter__menu tool-starter__tool__menu" aria-label="Tool actions" data-launch-mode-nav="tool">
41+
<button id="toolImportButton" type="button">Import</button>
42+
<button id="toolCopyJsonButton" type="button">Copy JSON</button>
43+
<button id="toolExportButton" type="button">JSON</button>
44+
</nav>
45+
4046
<nav class="tool-starter__menu tool-starter__workspace__menu" aria-label="Workspace actions" data-launch-mode-nav="workspace" hidden>
4147
<button id="workspaceImportManifestButton" type="button">Import manifest</button>
4248
<button id="workspaceCopyManifestButton" type="button">Copy manifest</button>
@@ -61,9 +67,9 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
6167
<input id="inputMappingV2CustomActionInput" type="text" autocomplete="off" placeholder="dash, interact, inspect">
6268
</label>
6369
<div class="input-mapping-v2__button-row">
64-
<button id="inputMappingV2AddActionButton" type="button">Add Action</button>
65-
<button id="inputMappingV2ClearActionButton" type="button">Clear Actions</button>
66-
<button id="inputMappingV2DeleteActionButton" type="button">Delete Action</button>
70+
<button id="inputMappingV2AddActionButton" type="button">Add</button>
71+
<button id="inputMappingV2ClearActionButton" type="button">Delete</button>
72+
<button id="inputMappingV2DeleteActionButton" type="button">Delete</button>
6773
</div>
6874
<p id="inputMappingV2ActionHint" class="tool-starter__hint">Default actions are engine-style action names sorted alphabetically.</p>
6975
</div>
@@ -83,6 +89,9 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
8389
</div>
8490
</section>
8591

92+
</aside>
93+
94+
<section class="tool-starter__panel tool-starter__panel--center" aria-label="Captured mappings">
8695
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
8796
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="gestureSetupContent">
8897
<span>Gestures</span>
@@ -110,9 +119,7 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
110119
<button id="inputMappingV2RefreshGamepadsButton" type="button">Refresh Gamepads</button>
111120
</div>
112121
</section>
113-
</aside>
114122

115-
<section class="tool-starter__panel tool-starter__panel--center" aria-label="Captured mappings">
116123
<section class="accordion-v2 tool-starter__accordion tool-starter__accordion--fill is-open" data-accordion-v2-open="true">
117124
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="mappingPanelContent">
118125
<span>Captured Mappings</span>
@@ -137,6 +144,16 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
137144
</div>
138145
</section>
139146

147+
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
148+
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="exportContent">
149+
<span>JSON</span>
150+
<span class="accordion-v2__icon" aria-hidden="true">+</span>
151+
</button>
152+
<div id="exportContent" class="accordion-v2__content">
153+
<pre id="inspectorOutput" class="tool-starter__output">{}</pre>
154+
</div>
155+
</section>
156+
140157
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
141158
<div class="accordion-v2__header tool-starter__status-accordion-header" aria-expanded="true" aria-controls="statusLogContent">
142159
<span>Status / Log</span>
@@ -149,20 +166,6 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
149166
<textarea id="statusLog" readonly rows="10" aria-label="Status log"></textarea>
150167
</div>
151168
</section>
152-
153-
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
154-
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="exportContent">
155-
<span>Export</span>
156-
<span class="accordion-v2__icon" aria-hidden="true">+</span>
157-
</button>
158-
<div id="exportContent" class="accordion-v2__content">
159-
<nav id="inputMappingV2ExportNav" class="tool-starter__menu tool-starter__tool__menu input-mapping-v2__export-menu" aria-label="Export actions" data-launch-mode-nav="tool">
160-
<button id="toolExportButton" type="button">Export</button>
161-
<button id="toolCopyJsonButton" type="button">Copy JSON</button>
162-
</nav>
163-
<pre id="inspectorOutput" class="tool-starter__output">{}</pre>
164-
</div>
165-
</section>
166169
</aside>
167170
</main>
168171
<script type="module" src="../../src/engine/theme/mount-shared-header.js"></script>

tools/input-mapping-v2/js/ToolStarterApp.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,13 @@ export class ToolStarterApp {
6161
onCopyJson: () => {
6262
void this.copyJson();
6363
},
64-
onExport: () => this.exportToolState()
64+
onExport: () => this.exportToolState(),
65+
onImport: () => this.importJson()
6566
});
6667
this.actionSelection.mount({
6768
onActionChanged: (actionId) => this.selectAction(actionId),
6869
onAddAction: (label) => this.addAction(label),
69-
onClearAction: () => this.clearSelectedAction(),
70+
onClearAction: () => this.deleteSelectedAction(),
7071
onDeleteAction: () => this.deleteSelectedAction()
7172
});
7273
this.capture.mount({
@@ -432,7 +433,11 @@ export class ToolStarterApp {
432433

433434
exportToolState() {
434435
this.inspector.showObject(this.state.toolState());
435-
this.statusLog.ok("Input Mapping V2 export preview written.");
436+
this.statusLog.ok("Input Mapping V2 JSON preview written.");
437+
}
438+
439+
importJson() {
440+
this.statusLog.warn("Input Mapping V2 Import is available through workspace launch data; standalone file import is not wired for this tool.");
436441
}
437442

438443
async copyJson() {

tools/input-mapping-v2/js/bootstrap.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ window.addEventListener("DOMContentLoaded", () => {
6161
exportControl: new ExportControl({
6262
copyJsonButton: requireElement("#toolCopyJsonButton"),
6363
exportButton: requireElement("#toolExportButton"),
64-
nav: requireElement("#inputMappingV2ExportNav")
64+
importButton: requireElement("#toolImportButton"),
65+
nav: requireElement("#inputMappingV2ToolNav")
6566
}),
6667
gamepadDiagnostics: new GamepadDiagnosticsControl(requireElement("#inputMappingV2Diagnostics")),
6768
gestureList: new GestureListControl(requireElement("#inputMappingV2GestureList")),

tools/input-mapping-v2/js/controls/ExportControl.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
export class ExportControl {
2-
constructor({ copyJsonButton, exportButton, locationRef = window.location, nav }) {
2+
constructor({ copyJsonButton, exportButton, importButton, locationRef = window.location, nav }) {
33
this.copyJsonButton = copyJsonButton;
44
this.exportButton = exportButton;
5+
this.importButton = importButton;
56
this.location = locationRef;
67
this.nav = nav;
78
}
89

9-
mount({ onCopyJson, onExport }) {
10+
mount({ onCopyJson, onExport, onImport }) {
1011
this.applyLaunchMode();
12+
this.importButton.addEventListener("click", onImport);
1113
this.exportButton.addEventListener("click", onExport);
1214
this.copyJsonButton.addEventListener("click", onCopyJson);
1315
}
1416

1517
setEnabled(isEnabled) {
18+
this.importButton.disabled = !isEnabled;
1619
this.exportButton.disabled = !isEnabled;
1720
this.copyJsonButton.disabled = !isEnabled;
1821
}

0 commit comments

Comments
 (0)