Skip to content

Commit 8b9af16

Browse files
committed
Fix Input Mapping V2 export manifest wiring and nav actions - PR_26140_116-fix-input-mapping-v2-export-manifest-and-nav
1 parent e0aad6a commit 8b9af16

7 files changed

Lines changed: 151 additions & 24 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Input Mapping V2 Export Manifest Nav Report
2+
3+
PR: `PR_26140_116-fix-input-mapping-v2-export-manifest-and-nav`
4+
5+
## Summary
6+
- Renamed the center workspace accordion from `Action Mappings` to `Action Mapping(s)`, including the related center panel and preview aria labels.
7+
- Updated `InputMappingState.payload()` so exported/copied JSON excludes actions whose `inputs` array is empty.
8+
- Changed JSON preview export to emit the actual filtered Input Mapping V2 payload, not a wrapper `toolState.payload` object.
9+
- Rewired Input Mapping V2 workspace nav Copy JSON and JSON buttons to the same real filtered payload actions as the tool nav.
10+
- Disabled Import in tool and workspace nav with an actionable reason because this tool imports through Workspace Manager `game.manifest.json` launch data.
11+
- Confirmed the active Asteroids `game.manifest.json` already includes `tools.input-mapping-v2`, and Workspace Manager V2 already discovers/launches it from the selected game manifest.
12+
13+
## Files Changed
14+
- `tools/input-mapping-v2/index.html`
15+
- `tools/input-mapping-v2/js/ToolStarterApp.js`
16+
- `tools/input-mapping-v2/js/controls/ActionNavControl.js`
17+
- `tools/input-mapping-v2/js/controls/ExportControl.js`
18+
- `tools/input-mapping-v2/js/services/InputMappingState.js`
19+
- `tests/playwright/tools/WorkspaceManagerV2.spec.mjs`
20+
- `docs/dev/commit_comment.txt`
21+
- `docs/dev/codex_commands.md`
22+
- `docs/dev/reports/input_mapping_v2_export_manifest_nav_report.md`
23+
24+
## Validation
25+
- PASS: `node --check tools/input-mapping-v2/js/services/InputMappingState.js`
26+
- PASS: `node --check tools/input-mapping-v2/js/ToolStarterApp.js`
27+
- PASS: `node --check tools/input-mapping-v2/js/controls/ExportControl.js`
28+
- PASS: `node --check tools/input-mapping-v2/js/controls/ActionNavControl.js`
29+
- PASS: `node --check tests/playwright/tools/WorkspaceManagerV2.spec.mjs`
30+
- PASS: node manifest assertion confirmed `games/Asteroids/game.manifest.json` contains `tools.input-mapping-v2` with the expected tool id and engine input model.
31+
- PASS: `npm run test:workspace-v2` passed 70/70 tests.
32+
33+
## Playwright Coverage Added/Updated
34+
- Tool nav shows Import, Copy JSON, JSON; Import is disabled with an actionable reason.
35+
- Workspace nav shows Import, Copy JSON, JSON when launched in workspace mode; Import is disabled with an actionable reason.
36+
- Workspace nav JSON preview and Copy JSON use the real filtered payload.
37+
- Empty default actions export as `actions: []` instead of empty action entries.
38+
- Mapped exports include only actions with inputs and exclude empty action tiles such as Confirm and Start.
39+
- Copy JSON writes the same filtered payload shown by JSON preview.
40+
- Active `game.manifest.json` includes `input-mapping-v2`, and existing Workspace Manager V2 coverage confirms Input Mapping V2 hydrates, appears as enabled, and launches from the selected game manifest.
41+
42+
## Coverage Report
43+
- Browser V8 coverage was generated by the workspace-v2 run.
44+
- Changed runtime JS coverage lines are present in `docs/dev/reports/playwright_v8_coverage_report.txt`:
45+
- `(90%) tools/input-mapping-v2/js/services/InputMappingState.js - changed JS file with browser V8 coverage`
46+
- `(94%) tools/input-mapping-v2/js/ToolStarterApp.js - changed JS file with browser V8 coverage`
47+
- `(100%) tools/input-mapping-v2/js/controls/ActionNavControl.js - changed JS file with browser V8 coverage`
48+
- `(100%) tools/input-mapping-v2/js/controls/ExportControl.js - changed JS file with browser V8 coverage`
49+
50+
## Manual Test Notes
51+
1. Open `tools/input-mapping-v2/index.html`.
52+
2. Confirm the center mapping accordion reads `Action Mapping(s)`.
53+
3. Confirm Import is disabled and its tooltip explains that import is through Workspace Manager `game.manifest.json` launch data.
54+
4. Add a tile, capture one input, and click JSON; expected JSON includes only actions with captured inputs.
55+
5. Click Copy JSON; expected clipboard content matches the filtered JSON preview.
56+
6. Open Workspace Manager V2, select Asteroids, and launch Input Mapping V2; expected result is that the tool loads from `game.manifest.json` and Return to Workspace remains available.
57+
58+
## Full Samples Smoke Test
59+
- Not run, per request and project instructions. This PR is scoped to Input Mapping V2 and Workspace V2 validation.

tests/playwright/tools/WorkspaceManagerV2.spec.mjs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2800,9 +2800,39 @@ test.describe("Workspace Manager V2 bootstrap", () => {
28002800
await expect(page.locator(".tool-starter__tool__menu")).toBeVisible();
28012801
await expect(page.locator('[data-launch-mode-nav="tool"] button')).toHaveText(["Import", "Copy JSON", "JSON"]);
28022802
await expect(page.locator("#toolExportToolStateButton")).toHaveCount(0);
2803+
await expect(page.locator("#toolImportButton")).toBeDisabled();
2804+
await expect(page.locator("#toolImportButton")).toHaveAttribute("title", /Import disabled: Input Mapping V2 imports through Workspace Manager game\.manifest launch data/);
28032805
await expect(page.locator(".tool-starter__workspace__menu")).toBeHidden();
2804-
await page.locator("#toolImportButton").click();
2805-
await expect(page.locator("#statusLog")).toHaveValue(/WARN Input Mapping V2 Import is available through workspace launch data/);
2806+
await page.goto(`${server.baseUrl}/tools/input-mapping-v2/index.html?launch=workspace`, { waitUntil: "networkidle" });
2807+
await expect(page.locator(".tool-starter__tool__menu")).toBeHidden();
2808+
await expect(page.locator(".tool-starter__workspace__menu")).toBeVisible();
2809+
await expect(page.locator('[data-launch-mode-nav="workspace"] button:not([hidden])')).toHaveText(["Import", "Copy JSON", "JSON"]);
2810+
await expect(page.locator("#workspaceImportManifestButton")).toBeDisabled();
2811+
await expect(page.locator("#workspaceImportManifestButton")).toHaveAttribute("title", /Import disabled: Input Mapping V2 imports through Workspace Manager game\.manifest launch data/);
2812+
await page.locator("#workspaceExportManifestButton").click();
2813+
const workspaceExportPayload = JSON.parse(await page.locator("#inspectorOutput").textContent());
2814+
expect(workspaceExportPayload).toMatchObject({
2815+
engineInputModel: "src/engine/input/InputMap",
2816+
toolId: "input-mapping-v2",
2817+
version: 1
2818+
});
2819+
expect(workspaceExportPayload.actions).toEqual([]);
2820+
await expect(page.locator("#statusLog")).toHaveValue(/OK Input Mapping V2 JSON preview written\./);
2821+
await page.evaluate(() => {
2822+
Object.defineProperty(navigator, "clipboard", {
2823+
configurable: true,
2824+
value: {
2825+
writeText: async (text) => {
2826+
window.__inputMappingV2WorkspaceClipboard = text;
2827+
}
2828+
}
2829+
});
2830+
});
2831+
await page.locator("#workspaceCopyManifestButton").click();
2832+
expect(JSON.parse(await page.evaluate(() => window.__inputMappingV2WorkspaceClipboard))).toEqual(workspaceExportPayload);
2833+
await expect(page.locator("#statusLog")).toHaveValue(/OK Mapping JSON copied\./);
2834+
await page.goto(`${server.baseUrl}/tools/input-mapping-v2/index.html`, { waitUntil: "networkidle" });
2835+
await expect(page.locator("body[data-tool-id='input-mapping-v2']")).toBeVisible();
28062836
const fullscreenLayout = await page.locator(".input-mapping-v2.tool-starter.app-shell").evaluate((shell) => {
28072837
const left = shell.querySelector(".tool-starter__panel--left").getBoundingClientRect();
28082838
const center = shell.querySelector(".tool-starter__panel--center").getBoundingClientRect();
@@ -2824,7 +2854,7 @@ test.describe("Workspace Manager V2 bootstrap", () => {
28242854
expect(fullscreenLayout.centerWidth).toBeGreaterThan(1000);
28252855
expect(Math.max(...fullscreenLayout.rightAccordionHeights) - Math.min(...fullscreenLayout.rightAccordionHeights)).toBeLessThanOrEqual(3);
28262856
await expect(page.locator(".tool-starter__panel--left > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Actions", "Devices"]);
2827-
await expect(page.locator(".tool-starter__panel--center > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Input Device(s)", "Capture Gestures", "Action Mappings"]);
2857+
await expect(page.locator(".tool-starter__panel--center > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Input Device(s)", "Capture Gestures", "Action Mapping(s)"]);
28282858
const centerAccordionLabels = await page.locator(".tool-starter__panel--center > .tool-starter__accordion > .accordion-v2__header > span:first-child").allTextContents();
28292859
expect(centerAccordionLabels.some((label) => /[{}]/.test(label))).toBe(false);
28302860
await expect(page.locator(".tool-starter__panel--right > .tool-starter__accordion > .accordion-v2__header > span:first-child")).toHaveText(["Diagnostics", "JSON", "Status / Log"]);
@@ -3482,7 +3512,19 @@ test.describe("Workspace Manager V2 bootstrap", () => {
34823512
});
34833513
expect(asteroidsManifestInputMapping.actions.map((action) => action.label)).toEqual(actionOptions);
34843514
await page.locator("#toolExportButton").click();
3485-
await expect(page.locator("#inspectorOutput")).toContainText('"payload"');
3515+
const exportedInputMappingPayload = JSON.parse(await page.locator("#inspectorOutput").textContent());
3516+
expect(exportedInputMappingPayload).toMatchObject({
3517+
engineInputModel: "src/engine/input/InputMap",
3518+
toolId: "input-mapping-v2",
3519+
version: 1
3520+
});
3521+
expect(exportedInputMappingPayload.payload).toBeUndefined();
3522+
expect(exportedInputMappingPayload.actions.length).toBeGreaterThan(0);
3523+
expect(exportedInputMappingPayload.actions.every((action) => action.inputs.length > 0)).toBe(true);
3524+
expect(exportedInputMappingPayload.actions.map((action) => action.action)).toContain("moveLeft");
3525+
expect(exportedInputMappingPayload.actions.map((action) => action.action)).not.toContain("confirm");
3526+
expect(exportedInputMappingPayload.actions.map((action) => action.action)).not.toContain("start");
3527+
expect(exportedInputMappingPayload.actions.some((action) => action.inputs.some((input) => input.binding === "KeyD"))).toBe(true);
34863528
await expect(page.locator("#statusLog")).toHaveValue(/OK Input Mapping V2 JSON preview written\./);
34873529
await page.evaluate(() => {
34883530
Object.defineProperty(navigator, "clipboard", {
@@ -3495,7 +3537,9 @@ test.describe("Workspace Manager V2 bootstrap", () => {
34953537
});
34963538
});
34973539
await page.locator("#toolCopyJsonButton").click();
3498-
expect(await page.evaluate(() => window.__inputMappingV2Clipboard)).toContain('"toolId": "input-mapping-v2"');
3540+
const copiedInputMappingPayload = JSON.parse(await page.evaluate(() => window.__inputMappingV2Clipboard));
3541+
expect(copiedInputMappingPayload).toEqual(exportedInputMappingPayload);
3542+
expect(copiedInputMappingPayload.actions.every((action) => action.inputs.length > 0)).toBe(true);
34993543
await expect(page.locator("#statusLog")).toHaveValue(/OK Mapping JSON copied\./);
35003544
await expect(page.locator("#inputMappingV2ClearActionButton")).toHaveCount(0);
35013545
expect(pageErrors).toEqual([]);

tools/input-mapping-v2/index.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
4444
</nav>
4545

4646
<nav class="tool-starter__menu tool-starter__workspace__menu" aria-label="Workspace actions" data-launch-mode-nav="workspace" hidden>
47-
<button id="workspaceImportManifestButton" type="button">Import manifest</button>
48-
<button id="workspaceCopyManifestButton" type="button">Copy manifest</button>
49-
<button id="workspaceExportManifestButton" type="button">Export manifest</button>
47+
<button id="workspaceImportManifestButton" type="button">Import</button>
48+
<button id="workspaceCopyManifestButton" type="button">Copy JSON</button>
49+
<button id="workspaceExportManifestButton" type="button">JSON</button>
5050
<button id="returnToWorkspaceButton" type="button" hidden>Return to Workspace</button>
5151
</nav>
5252

@@ -102,7 +102,7 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
102102

103103
</aside>
104104

105-
<section class="tool-starter__panel tool-starter__panel--center" aria-label="Action mappings">
105+
<section class="tool-starter__panel tool-starter__panel--center" aria-label="Action mapping(s)">
106106
<section class="accordion-v2 tool-starter__accordion is-open" data-accordion-v2-open="true">
107107
<div class="accordion-v2__header input-mapping-v2__capture-header" aria-expanded="true" aria-controls="captureInputContent">
108108
<span>Input Device(s)</span>
@@ -143,11 +143,11 @@ <h2 class="tools-platform-frame__eyebrow">Input Sources and Action Mapping</h2>
143143

144144
<section class="accordion-v2 tool-starter__accordion tool-starter__accordion--fill is-open" data-accordion-v2-open="true">
145145
<button class="accordion-v2__header" type="button" aria-expanded="true" aria-controls="mappingPanelContent">
146-
<span>Action Mappings</span>
146+
<span>Action Mapping(s)</span>
147147
<span class="accordion-v2__icon" aria-hidden="true">+</span>
148148
</button>
149149
<div id="mappingPanelContent" class="accordion-v2__content">
150-
<div id="previewOutput" class="tool-starter__preview input-mapping-v2__mapping-list" aria-label="Action input mappings">
150+
<div id="previewOutput" class="tool-starter__preview input-mapping-v2__mapping-list" aria-label="Action input mapping(s)">
151151
<p>No inputs captured yet.</p>
152152
</div>
153153
<hr class="input-mapping-v2__mapping-actions-separator">

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ export class ToolStarterApp {
7474
this.shell.mount();
7575
this.accordions.forEach((accordion) => accordion.mount());
7676
this.actionNav.mount({
77-
onWorkspaceCopyManifest: () => this.statusLog.ok("Input Mapping V2 workspace copy action is ready."),
78-
onWorkspaceExportManifest: () => this.statusLog.ok("Input Mapping V2 workspace export action is ready."),
79-
onWorkspaceImportManifest: () => this.statusLog.ok("Input Mapping V2 workspace import action is ready.")
77+
onWorkspaceCopyManifest: () => {
78+
void this.copyJson();
79+
},
80+
onWorkspaceExportManifest: () => this.exportToolState(),
81+
onWorkspaceImportManifest: () => this.importJson()
8082
});
8183
this.exportControl.mount({
8284
onCopyJson: () => {
@@ -1049,17 +1051,18 @@ export class ToolStarterApp {
10491051
}
10501052

10511053
exportToolState() {
1052-
this.inspector.showObject(this.state.toolState());
1054+
this.inspector.showObject(this.state.payload());
10531055
this.statusLog.ok("Input Mapping V2 JSON preview written.");
10541056
}
10551057

10561058
importJson() {
1057-
this.statusLog.warn("Input Mapping V2 Import is available through workspace launch data; standalone file import is not wired for this tool.");
1059+
this.statusLog.warn("Import disabled: Input Mapping V2 imports through Workspace Manager game.manifest launch data. Edit game.manifest.json or relaunch from Workspace Manager with updated tool data.");
10581060
}
10591061

10601062
async copyJson() {
1061-
const json = JSON.stringify(this.state.payload(), null, 2);
1062-
this.inspector.showObject(this.state.payload());
1063+
const payload = this.state.payload();
1064+
const json = JSON.stringify(payload, null, 2);
1065+
this.inspector.showObject(payload);
10631066
if (typeof this.window.navigator?.clipboard?.writeText !== "function") {
10641067
this.statusLog.warn("Mapping JSON preview written. Clipboard API is unavailable.");
10651068
this.refreshActions();

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const IMPORT_DISABLED_REASON = "Import disabled: Input Mapping V2 imports through Workspace Manager game.manifest launch data. Edit game.manifest.json or relaunch from Workspace Manager with updated tool data.";
2+
13
export class ActionNavControl {
24
constructor({
35
locationRef = window.location,
@@ -23,6 +25,7 @@ export class ActionNavControl {
2325
onWorkspaceImportManifest
2426
}) {
2527
this.applyLaunchMode();
28+
this.disableImportAction();
2629
this.workspaceImportManifestButton.addEventListener("click", onWorkspaceImportManifest);
2730
this.workspaceCopyManifestButton.addEventListener("click", onWorkspaceCopyManifest);
2831
this.workspaceExportManifestButton.addEventListener("click", onWorkspaceExportManifest);
@@ -45,10 +48,17 @@ export class ActionNavControl {
4548
}
4649

4750
setToolActionsEnabled(isEnabled) {
51+
this.disableImportAction();
4852
this.workspaceCopyManifestButton.disabled = !isEnabled;
4953
this.workspaceExportManifestButton.disabled = !isEnabled;
5054
}
5155

56+
disableImportAction() {
57+
this.workspaceImportManifestButton.disabled = true;
58+
this.workspaceImportManifestButton.dataset.disabledReason = IMPORT_DISABLED_REASON;
59+
this.workspaceImportManifestButton.title = IMPORT_DISABLED_REASON;
60+
}
61+
5262
workspaceManagerUrl() {
5363
const url = new URL("../workspace-manager-v2/index.html", this.location.href);
5464
const params = new URLSearchParams(this.location.search);

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const IMPORT_DISABLED_REASON = "Import disabled: Input Mapping V2 imports through Workspace Manager game.manifest launch data. Edit game.manifest.json or relaunch from Workspace Manager with updated tool data.";
2+
13
export class ExportControl {
24
constructor({ copyJsonButton, exportButton, importButton, locationRef = window.location, nav }) {
35
this.copyJsonButton = copyJsonButton;
@@ -9,13 +11,14 @@ export class ExportControl {
911

1012
mount({ onCopyJson, onExport, onImport }) {
1113
this.applyLaunchMode();
14+
this.disableImportAction();
1215
this.importButton.addEventListener("click", onImport);
1316
this.exportButton.addEventListener("click", onExport);
1417
this.copyJsonButton.addEventListener("click", onCopyJson);
1518
}
1619

1720
setEnabled(isEnabled) {
18-
this.importButton.disabled = !isEnabled;
21+
this.disableImportAction();
1922
this.exportButton.disabled = !isEnabled;
2023
this.copyJsonButton.disabled = !isEnabled;
2124
}
@@ -24,4 +27,10 @@ export class ExportControl {
2427
const params = new URLSearchParams(this.location.search);
2528
this.nav.hidden = params.get("launch") === "workspace";
2629
}
30+
31+
disableImportAction() {
32+
this.importButton.disabled = true;
33+
this.importButton.dataset.disabledReason = IMPORT_DISABLED_REASON;
34+
this.importButton.title = IMPORT_DISABLED_REASON;
35+
}
2736
}

tools/input-mapping-v2/js/services/InputMappingState.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,13 @@ export class InputMappingState {
178178
toolId: "input-mapping-v2",
179179
version: 1,
180180
engineInputModel: "src/engine/input/InputMap",
181-
actions: this.actions().map((action) => ({
182-
action: action.id,
183-
label: action.label,
184-
inputs: action.inputs.map(schemaInput)
185-
}))
181+
actions: this.actions()
182+
.filter((action) => action.inputs.length > 0)
183+
.map((action) => ({
184+
action: action.id,
185+
label: action.label,
186+
inputs: action.inputs.map(schemaInput)
187+
}))
186188
};
187189
}
188190

0 commit comments

Comments
 (0)