diff --git a/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-branch-validation.txt b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-branch-validation.txt new file mode 100644 index 000000000..32297dff1 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-branch-validation.txt @@ -0,0 +1,6 @@ +Branch Validation: PASS + +PASS - Current branch: pr/26174-ALFA-007-game-journey-count-ui-polish. +PASS - Stack base: pr/26174-ALFA-006-game-hub-empty-and-error-states. +PASS - Changes are scoped to Game Journey count UI polish, targeted tests, and required reports. +PASS - No merge to main performed. diff --git a/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-manual-validation-notes.txt b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-manual-validation-notes.txt new file mode 100644 index 000000000..2e469a2c1 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-manual-validation-notes.txt @@ -0,0 +1,6 @@ +Manual Validation Notes: PASS + +PASS - Confirmed target labels render Hero [1], Enemy [4], Boss [1], Background [3], Music [5]. +PASS - Confirmed Hero count preview updates to [2] after edit and remains [2] after reload. +PASS - Confirmed updateRecommendedTarget API/service method is called with ["hero", 2]. +PASS - Confirmed no checkbox inputs were introduced. diff --git a/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-requirement-checklist.txt b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-requirement-checklist.txt new file mode 100644 index 000000000..231aafe0a --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-requirement-checklist.txt @@ -0,0 +1,6 @@ +Requirement Checklist: PASS + +PASS - Polished count-based Journey inputs with visible [count] labels. +PASS - Kept numeric counts and verified no checkbox inputs in the target model. +PASS - Preserved target/bucket order: Hero, Enemy, Boss, Background, Music. +PASS - Persisted edits through the existing API/service contract only. diff --git a/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-validation-lane.txt b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-validation-lane.txt new file mode 100644 index 000000000..957b15ec5 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-validation-lane.txt @@ -0,0 +1,7 @@ +Validation Lane: PASS + +Targeted Playwright impacted lane: +PASS - npx playwright test tests/playwright/tools/GameJourneyTool.spec.mjs -g "Game Journey progress dashboard summarizes completion metrics" + +Notes: +- Full workspace smoke was not run; targeted impacted Playwright validation was used per request. diff --git a/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish.md b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish.md new file mode 100644 index 000000000..37e19c4db --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish.md @@ -0,0 +1,16 @@ +# PR_26174_ALFA_007-game-journey-count-ui-polish + +## Purpose + +Polish count-based Game Journey inputs. + +## Summary + +- Added live [count] previews to Game Journey recommended target labels. +- Clarified the recommended target table header from Suggested to Count. +- Kept native numeric inputs and added numeric input hints. +- Extended targeted Playwright coverage for order, no-checkbox model, live preview updates, and API/service contract persistence. + +## Validation + +PASS - `npx playwright test tests/playwright/tools/GameJourneyTool.spec.mjs -g "Game Journey progress dashboard summarizes completion metrics"` diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 32a1eda1b..a2d40e32e 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,9 +1,9 @@ -toolbox/game-hub/game-hub.js -tests/playwright/tools/GameHubMockRepository.spec.mjs +toolbox/game-journey/game-journey.js +tests/playwright/tools/GameJourneyTool.spec.mjs docs_build/dev/reports/codex_review.diff docs_build/dev/reports/codex_changed_files.txt -docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states.md -docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states-branch-validation.txt -docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states-requirement-checklist.txt -docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states-validation-lane.txt -docs_build/dev/reports/PR_26174_ALFA_006-game-hub-empty-and-error-states-manual-validation-notes.txt +docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish.md +docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-branch-validation.txt +docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-requirement-checklist.txt +docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-validation-lane.txt +docs_build/dev/reports/PR_26174_ALFA_007-game-journey-count-ui-polish-manual-validation-notes.txt diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 41724e43a..7d7c6c403 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,147 +1,132 @@ -diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs -index 2d583e1d7..e11631269 100644 ---- a/tests/playwright/tools/GameHubMockRepository.spec.mjs -+++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs -@@ -322,6 +322,89 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page } +diff --git a/tests/playwright/tools/GameJourneyTool.spec.mjs b/tests/playwright/tools/GameJourneyTool.spec.mjs +index e9005f08b..586e7521f 100644 +--- a/tests/playwright/tools/GameJourneyTool.spec.mjs ++++ b/tests/playwright/tools/GameJourneyTool.spec.mjs +@@ -69,7 +69,6 @@ async function openRepoPage(page, pathName, options = {}) { + page.on("requestfailed", (request) => { + failedRequests.push(`FAILED ${request.url()}`); + }); +- + if (collectCoverage) { + await workspaceV2CoverageReporter.start(page); } - }); +@@ -258,6 +257,7 @@ test("Game Journey progress dashboard summarizes completion metrics", async ({ p + const failedRequests = []; + const pageErrors = []; + const consoleErrors = []; ++ const recommendedTargetRequests = []; -+test("Game Hub shows a creator-safe empty state when no projects exist", async ({ page }) => { -+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/getActiveGame", async (route) => { -+ await route.fulfill({ -+ body: JSON.stringify({ -+ data: { result: null }, -+ ok: true, -+ rule: "Browser -> Server API -> Data Source", -+ }), -+ contentType: "application/json; charset=utf-8", -+ status: 200, -+ }); + page.on("pageerror", (error) => { + pageErrors.push(error.message); +@@ -275,6 +275,12 @@ test("Game Journey progress dashboard summarizes completion metrics", async ({ p + page.on("requestfailed", (request) => { + failedRequests.push(`FAILED ${request.url()}`); + }); ++ page.on("request", (request) => { ++ const requestUrl = request.url(); ++ if (requestUrl.includes("/api/toolbox/game-journey/repositories/") && requestUrl.includes("/methods/updateRecommendedTarget")) { ++ recommendedTargetRequests.push(request.postDataJSON()); ++ } + }); -+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/getGameProgress", async (route) => { -+ await route.fulfill({ -+ body: JSON.stringify({ -+ data: { -+ result: { -+ gameStatus: "No Game", -+ gameProgress: "No active game", -+ publishingProgress: "Not started", -+ currentFocus: "Create a game", -+ recommendedNextTool: "Game Hub", -+ progressChecklist: [], -+ }, -+ }, -+ ok: true, -+ rule: "Browser -> Server API -> Data Source", -+ }), -+ contentType: "application/json; charset=utf-8", -+ status: 200, -+ }); -+ }); -+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/listGames", async (route) => { -+ await route.fulfill({ -+ body: JSON.stringify({ -+ data: { result: [] }, -+ ok: true, -+ rule: "Browser -> Server API -> Data Source", -+ }), -+ contentType: "application/json; charset=utf-8", -+ status: 200, -+ }); -+ }); -+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() }); -+ -+ try { -+ await expect(page.locator("[data-active-game-name]")).toHaveText("No game open"); -+ await expect(page.locator("[data-game-list] [data-game-list-status='empty']")).toHaveText("No Game Hub projects yet. Create a game to start building."); -+ await expect(page.locator("[data-game-list] [data-game-row]")).toHaveCount(0); -+ await expect(page.locator("[data-game-hub-log]")).not.toContainText(/server|API|repository|database|stack|error/i); -+ await expectNoPageFailures(failures); -+ } finally { -+ await failures.server.close(); -+ } -+}); -+ -+test("Game Hub shows a creator-safe unavailable state when project list API fails", async ({ page }) => { -+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/listGames", async (route) => { -+ await route.fulfill({ -+ body: JSON.stringify({ -+ error: "postgres://service-role-secret@internal.example:5432/gamefoundry failed with stack trace", -+ ok: false, -+ rule: "Browser -> Server API -> Data Source", -+ }), -+ contentType: "application/json; charset=utf-8", -+ status: 503, -+ }); -+ }); -+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() }); -+ -+ try { -+ expect(failures.failedRequests.some((request) => request.includes("503") && request.includes("/methods/listGames"))).toBe(true); -+ await expect(page.locator("[data-game-list] [data-game-list-status='unavailable']")).toHaveText("Game Hub projects are temporarily unavailable. Refresh the page or try again shortly."); -+ await expect(page.locator("[data-game-list] [data-game-row]")).toHaveCount(0); -+ await expect(page.locator("[data-game-hub-log]")).toHaveText("Game Hub projects are temporarily unavailable. Refresh the page or try again shortly."); -+ await expect(page.locator("main")).not.toContainText(/postgres|service-role-secret|internal\.example|stack trace|repository|database/i); -+ expect(failures.pageErrors).toEqual([]); -+ expect(failures.consoleErrors.filter((message) => !message.includes("status of 503"))).toEqual([]); -+ } finally { -+ await failures.server.close(); -+ } -+}); -+ - test("Game Hub shows active-game errors without throwing", async ({ page }) => { - await page.route("**/api/toolbox/game-hub/repositories/*/methods/getActiveGame", async (route) => { - await route.fulfill({ -diff --git a/toolbox/game-hub/game-hub.js b/toolbox/game-hub/game-hub.js -index d2260fe04..2d6f321e4 100644 ---- a/toolbox/game-hub/game-hub.js -+++ b/toolbox/game-hub/game-hub.js -@@ -217,6 +217,14 @@ function createGameButton(game, isActive) { - return button; - } - -+function createGameListStatus(message, state) { -+ const emptyState = document.createElement("p"); -+ emptyState.className = "status"; -+ emptyState.dataset.gameListStatus = state; -+ emptyState.textContent = message; -+ return emptyState; -+} -+ - function renderProjectInformation(activeGame, currentMember, progress) { - if (!elements.projectRecordsTable) { - return; -@@ -252,22 +260,23 @@ function renderGameList() { - const activeGame = normalizeActiveGame(repository.getActiveGame()); - const gameUserKey = currentGameUserKey(activeGame); - const listResult = repository.listGames(gameUserKey ? { userKey: gameUserKey } : {}); -- const games = Array.isArray(listResult) ? listResult : []; -- if (!Array.isArray(listResult) && !reportRepositoryError(listResult, "Game list")) { -- setStatusLog("Game list is temporarily unavailable. Refresh the page or try again shortly."); -- } - elements.gameList.replaceChildren(); - -- if (games.length === 0) { -- const emptyState = document.createElement("p"); -- emptyState.className = "status"; -- emptyState.textContent = "No games. Create a game to continue."; -- elements.gameList.append(emptyState); -+ if (!Array.isArray(listResult)) { -+ const message = "Game Hub projects are temporarily unavailable. Refresh the page or try again shortly."; -+ reportRepositoryError(listResult, "Game Hub projects"); -+ setStatusLog(message); -+ elements.gameList.append(createGameListStatus(message, "unavailable")); -+ return; + try { + await workspaceV2CoverageReporter.start(page); +@@ -332,12 +338,23 @@ test("Game Journey progress dashboard summarizes completion metrics", async ({ p + "Next action: mark one finished section item complete so overall progress can rise above 0%.", + ]); + await expect(page.locator("[data-journey-recommended-target]")).toHaveCount(5); ++ const recommendedTargetOrder = await page.locator("[data-journey-recommended-target]").evaluateAll((rows) => ( ++ rows.map((row) => row.dataset.journeyRecommendedTarget) ++ )); ++ expect(recommendedTargetOrder).toEqual([ ++ "hero", ++ "enemy", ++ "boss", ++ "background", ++ "music", ++ ]); ++ await expect(page.locator("[data-journey-recommended-targets] th")).toHaveText(["Target", "Section", "Count"]); + await expect(page.locator("[data-journey-recommended-target] td:first-child")).toHaveText([ +- "Hero", +- "Enemy", +- "Boss", +- "Background", +- "Music", ++ "Hero [1]", ++ "Enemy [4]", ++ "Boss [1]", ++ "Background [3]", ++ "Music [5]", + ]); + await expect(page.locator("[data-journey-recommended-target='hero'] td").nth(1)).toHaveText("Objects"); + await expect(page.locator("[data-journey-recommended-target='enemy'] td").nth(1)).toHaveText("Objects"); +@@ -349,11 +366,19 @@ test("Game Journey progress dashboard summarizes completion metrics", async ({ p + await expect(page.locator("[data-journey-target-input='boss']")).toHaveValue("1"); + await expect(page.locator("[data-journey-target-input='background']")).toHaveValue("3"); + await expect(page.locator("[data-journey-target-input='music']")).toHaveValue("5"); ++ await expect(page.locator("[data-journey-recommended-target] input[type='checkbox']")).toHaveCount(0); ++ await expect(page.locator("[data-journey-target-input='hero']")).toHaveAttribute("type", "number"); ++ await expect(page.locator("[data-journey-target-input='hero']")).toHaveAttribute("inputmode", "numeric"); ++ await expect(page.locator("[data-journey-target-input='hero']")).toHaveAttribute("aria-label", "Hero count"); + await page.locator("[data-journey-target-input='hero']").fill("2"); + await expect(page.locator("[data-journey-target-status]")).toHaveText("Saved Hero target at 2."); + await expect(page.locator("[data-journey-target-input='hero']")).toHaveValue("2"); ++ await expect(page.locator("[data-journey-target-count-preview='hero']")).toHaveText(" [2]"); ++ expect(recommendedTargetRequests).toHaveLength(1); ++ expect(recommendedTargetRequests[0].args).toEqual(["hero", 2]); + await page.reload({ waitUntil: "networkidle" }); + await expect(page.locator("[data-journey-target-input='hero']")).toHaveValue("2"); ++ await expect(page.locator("[data-journey-target-count-preview='hero']")).toHaveText(" [2]"); + const repositoryData = await fetchApiData(server, "/api/toolbox/game-journey/repositories", { + body: JSON.stringify({ options: {} }), + method: "POST", +diff --git a/toolbox/game-journey/game-journey.js b/toolbox/game-journey/game-journey.js +index c2b2f876c..332274910 100644 +--- a/toolbox/game-journey/game-journey.js ++++ b/toolbox/game-journey/game-journey.js +@@ -1161,7 +1161,7 @@ function renderRecommendedTargets() { + table.setAttribute("aria-label", "Game Journey recommended planning targets"); + const head = createElement("thead"); + const headRow = createElement("tr"); +- ["Target", "Section", "Suggested"].forEach((heading) => { ++ ["Target", "Section", "Count"].forEach((heading) => { + const cell = createElement("th", { text: heading }); + cell.scope = "col"; + headRow.append(cell); +@@ -1169,16 +1169,22 @@ function renderRecommendedTargets() { + head.append(headRow); + const body = createElement("tbody"); + targets.forEach((target) => { ++ const targetCount = recommendedTargetValues.get(target.key) ?? target.suggestedCount; + const row = createElement("tr"); + row.dataset.journeyRecommendedTarget = target.key; +- const labelCell = createElement("td", { text: target.label }); ++ const labelCell = createElement("td"); ++ const label = createElement("span", { text: target.label }); ++ const countPreview = createElement("span", { text: ` [${targetCount}]` }); ++ countPreview.dataset.journeyTargetCountPreview = target.key; ++ labelCell.append(label, countPreview); + const sectionCell = createElement("td", { text: target.sectionName }); + const input = document.createElement("input"); + input.type = "number"; ++ input.inputMode = "numeric"; + input.min = "0"; + input.step = "1"; +- input.value = String(recommendedTargetValues.get(target.key) ?? target.suggestedCount); +- input.setAttribute("aria-label", `${target.label} suggested target`); ++ input.value = String(targetCount); ++ input.setAttribute("aria-label", `${target.label} count`); + input.dataset.journeyTargetInput = target.key; + const inputCell = createElement("td"); + inputCell.append(input); +@@ -1773,6 +1779,10 @@ recommendedTargets?.addEventListener("input", (event) => { + const savedValue = normalizeTargetCount(updated.suggestedCount); + recommendedTargetValues.set(target.key, savedValue); + input.value = String(savedValue); ++ const preview = recommendedTargets.querySelector(`[data-journey-target-count-preview='${target.key}']`); ++ if (preview) { ++ preview.textContent = ` [${savedValue}]`; + } -+ -+ if (listResult.length === 0) { -+ elements.gameList.append(createGameListStatus("No Game Hub projects yet. Create a game to start building.", "empty")); - return; + if (recommendedTargetStatus) { + recommendedTargetStatus.textContent = `Saved ${target.label} target at ${savedValue}.`; } - -- games.forEach((game) => { -+ listResult.forEach((game) => { - const row = document.createElement("article"); - row.className = "callout"; - row.dataset.gameRow = game.id; diff --git a/tests/playwright/tools/GameJourneyTool.spec.mjs b/tests/playwright/tools/GameJourneyTool.spec.mjs index e9005f08b..586e7521f 100644 --- a/tests/playwright/tools/GameJourneyTool.spec.mjs +++ b/tests/playwright/tools/GameJourneyTool.spec.mjs @@ -69,7 +69,6 @@ async function openRepoPage(page, pathName, options = {}) { page.on("requestfailed", (request) => { failedRequests.push(`FAILED ${request.url()}`); }); - if (collectCoverage) { await workspaceV2CoverageReporter.start(page); } @@ -258,6 +257,7 @@ test("Game Journey progress dashboard summarizes completion metrics", async ({ p const failedRequests = []; const pageErrors = []; const consoleErrors = []; + const recommendedTargetRequests = []; page.on("pageerror", (error) => { pageErrors.push(error.message); @@ -275,6 +275,12 @@ test("Game Journey progress dashboard summarizes completion metrics", async ({ p page.on("requestfailed", (request) => { failedRequests.push(`FAILED ${request.url()}`); }); + page.on("request", (request) => { + const requestUrl = request.url(); + if (requestUrl.includes("/api/toolbox/game-journey/repositories/") && requestUrl.includes("/methods/updateRecommendedTarget")) { + recommendedTargetRequests.push(request.postDataJSON()); + } + }); try { await workspaceV2CoverageReporter.start(page); @@ -332,12 +338,23 @@ test("Game Journey progress dashboard summarizes completion metrics", async ({ p "Next action: mark one finished section item complete so overall progress can rise above 0%.", ]); await expect(page.locator("[data-journey-recommended-target]")).toHaveCount(5); + const recommendedTargetOrder = await page.locator("[data-journey-recommended-target]").evaluateAll((rows) => ( + rows.map((row) => row.dataset.journeyRecommendedTarget) + )); + expect(recommendedTargetOrder).toEqual([ + "hero", + "enemy", + "boss", + "background", + "music", + ]); + await expect(page.locator("[data-journey-recommended-targets] th")).toHaveText(["Target", "Section", "Count"]); await expect(page.locator("[data-journey-recommended-target] td:first-child")).toHaveText([ - "Hero", - "Enemy", - "Boss", - "Background", - "Music", + "Hero [1]", + "Enemy [4]", + "Boss [1]", + "Background [3]", + "Music [5]", ]); await expect(page.locator("[data-journey-recommended-target='hero'] td").nth(1)).toHaveText("Objects"); await expect(page.locator("[data-journey-recommended-target='enemy'] td").nth(1)).toHaveText("Objects"); @@ -349,11 +366,19 @@ test("Game Journey progress dashboard summarizes completion metrics", async ({ p await expect(page.locator("[data-journey-target-input='boss']")).toHaveValue("1"); await expect(page.locator("[data-journey-target-input='background']")).toHaveValue("3"); await expect(page.locator("[data-journey-target-input='music']")).toHaveValue("5"); + await expect(page.locator("[data-journey-recommended-target] input[type='checkbox']")).toHaveCount(0); + await expect(page.locator("[data-journey-target-input='hero']")).toHaveAttribute("type", "number"); + await expect(page.locator("[data-journey-target-input='hero']")).toHaveAttribute("inputmode", "numeric"); + await expect(page.locator("[data-journey-target-input='hero']")).toHaveAttribute("aria-label", "Hero count"); await page.locator("[data-journey-target-input='hero']").fill("2"); await expect(page.locator("[data-journey-target-status]")).toHaveText("Saved Hero target at 2."); await expect(page.locator("[data-journey-target-input='hero']")).toHaveValue("2"); + await expect(page.locator("[data-journey-target-count-preview='hero']")).toHaveText(" [2]"); + expect(recommendedTargetRequests).toHaveLength(1); + expect(recommendedTargetRequests[0].args).toEqual(["hero", 2]); await page.reload({ waitUntil: "networkidle" }); await expect(page.locator("[data-journey-target-input='hero']")).toHaveValue("2"); + await expect(page.locator("[data-journey-target-count-preview='hero']")).toHaveText(" [2]"); const repositoryData = await fetchApiData(server, "/api/toolbox/game-journey/repositories", { body: JSON.stringify({ options: {} }), method: "POST", diff --git a/toolbox/game-journey/game-journey.js b/toolbox/game-journey/game-journey.js index c2b2f876c..332274910 100644 --- a/toolbox/game-journey/game-journey.js +++ b/toolbox/game-journey/game-journey.js @@ -1161,7 +1161,7 @@ function renderRecommendedTargets() { table.setAttribute("aria-label", "Game Journey recommended planning targets"); const head = createElement("thead"); const headRow = createElement("tr"); - ["Target", "Section", "Suggested"].forEach((heading) => { + ["Target", "Section", "Count"].forEach((heading) => { const cell = createElement("th", { text: heading }); cell.scope = "col"; headRow.append(cell); @@ -1169,16 +1169,22 @@ function renderRecommendedTargets() { head.append(headRow); const body = createElement("tbody"); targets.forEach((target) => { + const targetCount = recommendedTargetValues.get(target.key) ?? target.suggestedCount; const row = createElement("tr"); row.dataset.journeyRecommendedTarget = target.key; - const labelCell = createElement("td", { text: target.label }); + const labelCell = createElement("td"); + const label = createElement("span", { text: target.label }); + const countPreview = createElement("span", { text: ` [${targetCount}]` }); + countPreview.dataset.journeyTargetCountPreview = target.key; + labelCell.append(label, countPreview); const sectionCell = createElement("td", { text: target.sectionName }); const input = document.createElement("input"); input.type = "number"; + input.inputMode = "numeric"; input.min = "0"; input.step = "1"; - input.value = String(recommendedTargetValues.get(target.key) ?? target.suggestedCount); - input.setAttribute("aria-label", `${target.label} suggested target`); + input.value = String(targetCount); + input.setAttribute("aria-label", `${target.label} count`); input.dataset.journeyTargetInput = target.key; const inputCell = createElement("td"); inputCell.append(input); @@ -1773,6 +1779,10 @@ recommendedTargets?.addEventListener("input", (event) => { const savedValue = normalizeTargetCount(updated.suggestedCount); recommendedTargetValues.set(target.key, savedValue); input.value = String(savedValue); + const preview = recommendedTargets.querySelector(`[data-journey-target-count-preview='${target.key}']`); + if (preview) { + preview.textContent = ` [${savedValue}]`; + } if (recommendedTargetStatus) { recommendedTargetStatus.textContent = `Saved ${target.label} target at ${savedValue}.`; }