diff --git a/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup.md b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup.md new file mode 100644 index 000000000..e52b1f4ea --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup.md @@ -0,0 +1,25 @@ +# PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup + +## Summary + +Updated Game Hub parent table and save behavior for the ALFA_017 stack item. + +## Implementation + +- Guest Add/Edit rows remain reachable for browsing, but Add and Edit Save buttons redirect to `account/sign-in.html`. +- Renamed the current role side control area to a `Game Crew` accordion. +- Removed Owner from displayed parent table headers, parent rows, add rows, edit rows, and expanded row colspan. +- Kept parent game rows with Source Idea and Readiness Output child rows/tables. +- Removed the instructional copy from the center panel. +- Matched parent table action buttons to compact game button sizing. + +## Scope Control + +- Preserved existing API/service contract. +- Did not add browser-owned product data. +- Did not add readiness math. +- Did not modify table-first governance content. + +## ZIP + +- `tmp/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_delta.zip` diff --git a/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_branch-validation.txt b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_branch-validation.txt new file mode 100644 index 000000000..a26502903 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_branch-validation.txt @@ -0,0 +1,14 @@ +Branch validation: PASS + +Branch: +pr/26174-ALFA-017-game-hub-guest-save-and-crew-cleanup + +Base stack branch: +pr/26174-ALFA-016-game-hub-row-edit-add-selected-state + +Checks: +- Current branch is the ALFA_017 branch: PASS +- Worktree was clean before ALFA_017 edits: PASS +- Scope limited to Game Hub page/script, targeted Playwright coverage, and required reports: PASS +- No protected Project Instructions changes: PASS +- No merge to main performed: PASS diff --git a/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_manual-validation-notes.txt b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_manual-validation-notes.txt new file mode 100644 index 000000000..0c99d0f04 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_manual-validation-notes.txt @@ -0,0 +1,9 @@ +Manual validation notes: PASS + +- Reviewed `docs_build/dev/ProjectInstructions/addendums/table_first_ui.md` and applied the game-row parent table pattern. +- Confirmed the Game Hub center panel no longer includes the removed instructional copy. +- Confirmed parent table headers display only Game, Purpose, Status, and Actions. +- Confirmed parent rows no longer display Owner while keeping owner fields available to existing repository data. +- Confirmed Source Idea and Readiness Output remain expanded child rows/tables under each game parent row. +- Confirmed guest Add/Edit Save controls redirect to `account/sign-in.html`. +- Confirmed Add, Edit, Save, and Cancel actions use compact button sizing consistent with game buttons. diff --git a/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_requirement-checklist.txt b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_requirement-checklist.txt new file mode 100644 index 000000000..10230f3b8 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_requirement-checklist.txt @@ -0,0 +1,16 @@ +Requirement checklist: PASS + +- As a guest, clicking any Save button redirects to account/sign-in.html: PASS +- Move/rename current user role accordion to Game Crew: PASS +- Remove Owner from the displayed parent table fields: PASS +- Parent table columns are Game, Purpose, Status, Actions: PASS +- Owner remains implicit and is not displayed in the parent table: PASS +- Action buttons match the same scale/height as the game buttons: PASS +- Removed instructional copy: PASS +- Preserve Game row parent structure: PASS +- Preserve Source Idea child row/table: PASS +- Preserve Readiness Output child row/table: PASS +- Preserve API/service contract: PASS +- No browser-owned product data: PASS +- No silent fallbacks: PASS +- Follow table_first_ui.md: PASS diff --git a/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_validation-lane.txt b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_validation-lane.txt new file mode 100644 index 000000000..8c37f1240 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_validation-lane.txt @@ -0,0 +1,12 @@ +Validation lane: PASS + +Commands: +- `git diff --check -- toolbox/game-hub/index.html toolbox/game-hub/game-hub.js tests/playwright/tools/GameHubMockRepository.spec.mjs` + - PASS +- `node --check toolbox/game-hub/game-hub.js` + - PASS +- `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub"` + - PASS, 11 passed + +Notes: +- A broader unscoped run of `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs` was attempted before the final targeted lane. It reported 12 passed and 2 failed. One failure was the ALFA_017 guest redirect assertion and was fixed. The remaining failure was outside this PR's surface: `Toolbox member-role filters focus tools without exposing admin-only controls` received existing `500 /api/game-journey/completion-metrics` responses. diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 5e81caa5c..f7be6c46d 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,3 +1,10 @@ -assets/theme-v2/css/tables.css +docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup.md +docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_branch-validation.txt +docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_manual-validation-notes.txt +docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_requirement-checklist.txt +docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_validation-lane.txt +docs_build/dev/reports/codex_changed_files.txt +docs_build/dev/reports/codex_review.diff tests/playwright/tools/GameHubMockRepository.spec.mjs toolbox/game-hub/game-hub.js +toolbox/game-hub/index.html diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index f6a9821e0..882ec504b 100644 Binary files a/docs_build/dev/reports/codex_review.diff and b/docs_build/dev/reports/codex_review.diff differ diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs index 2363ff88d..40f1bc19e 100644 --- a/tests/playwright/tools/GameHubMockRepository.spec.mjs +++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs @@ -249,7 +249,8 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { try { await expect(page.locator(".tool-workspace")).toBeVisible(); await expect(page.locator("style, [style], script:not([src])")).toHaveCount(0); - await expect(page.getByRole("button", { name: "Add Game" })).toHaveClass("btn"); + await expect(page.getByRole("button", { name: "Add Game" })).toHaveClass(/\bbtn\b/); + await expect(page.getByRole("button", { name: "Add Game" })).toHaveClass(/\bbtn--compact\b/); await expect(page.getByRole("button", { name: "Add Game" })).toBeEnabled(); await expect(page.getByLabel("Game Name")).toHaveCount(0); await expect(page.getByLabel("Game Purpose")).toHaveCount(0); @@ -257,7 +258,9 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await expect(page.getByRole("button", { name: "Delete Open Game" })).toHaveClass("btn"); await expect(page.getByRole("button", { name: "Delete Open Game" })).toBeEnabled(); await expect(page.locator("summary").filter({ hasText: /^Game Setup$/ })).toHaveCount(0); + await expect(page.locator("summary").filter({ hasText: /^Game Crew$/ })).toHaveCount(1); await expect(page.getByRole("link", { name: "Open Game Journey" })).toHaveCount(0); + await expect(page.locator(".tool-center-panel")).not.toContainText("Review games in the parent table"); await expect(page.locator("[data-project-record-status]")).toHaveText("Game table loaded."); await expect(page.locator("[data-game-project-information]")).toHaveCount(0); await expect(page.locator("[data-project-records-table]")).toHaveCount(0); @@ -281,12 +284,12 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { "Game", "Purpose", "Status", - "Owner", "Actions", ]); - await expect(page.locator("[data-game-rows-table='true'] thead")).not.toContainText(/Role|Next Tool/); + await expect(page.locator("[data-game-rows-table='true'] thead")).not.toContainText(/Owner|Role|Next Tool/); const demoGameRow = page.locator("[data-game-row='demo-game']"); - await expect(demoGameRow.locator("td")).toHaveText(["Game", "Under Construction", "User 1", "Edit"]); + await expect(demoGameRow.locator("td")).toHaveText(["Game", "Under Construction", "Edit"]); + await expect(demoGameRow).not.toContainText("User 1"); await expect(demoGameRow).toHaveAttribute("data-game-active", "true"); await expect(demoGameRow).toHaveAttribute("aria-current", "true"); await expect(demoGameRow.locator("th[data-game-active-cell='true']")).toContainText("Demo Game"); @@ -303,9 +306,11 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await expect(demoGameRow.locator("> .status")).toHaveCount(0); await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "false"); await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).not.toHaveClass(/primary/); + await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveClass(/\bbtn--compact\b/); await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-current", "true"); await expect(demoGameRow.getByRole("button", { name: "Edit Demo Game" })).toHaveText("Edit"); await expect(demoGameRow.getByRole("button", { name: "Edit Demo Game" })).not.toHaveClass(/primary/); + await expect(demoGameRow.getByRole("button", { name: "Edit Demo Game" })).toHaveClass(/\bbtn--compact\b/); await expect(demoGameRow.getByRole("button", { name: "Edit Demo Game" })).not.toHaveAttribute("aria-current", "true"); await demoGameRow.locator("[data-game-toggle='demo-game']").click(); await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "true"); @@ -336,6 +341,8 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await page.getByRole("button", { name: "Add Game" }).click(); const addGameRow = page.locator("[data-game-add-row='input']"); await expect(addGameRow.locator("[data-game-action]")).toHaveText(["Save", "Cancel"]); + await expect(addGameRow.getByRole("button", { name: "Save" })).toHaveClass(/\bbtn--compact\b/); + await expect(addGameRow.locator("td")).toHaveCount(3); await addGameRow.getByLabel("Game").fill("Launch Test Game"); await addGameRow.getByLabel("Purpose").selectOption("Learning Game"); await addGameRow.getByLabel("Status").selectOption("Ready for Testing"); @@ -351,6 +358,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await page.getByRole("button", { name: "Edit Launch Test Game" }).click(); const editGameRow = page.locator("[data-game-edit-row='launch-test-game-1']"); await expect(editGameRow.locator("[data-game-action]")).toHaveText(["Save", "Cancel"]); + await expect(editGameRow.getByRole("button", { name: "Save" })).toHaveClass(/\bbtn--compact\b/); await expect(editGameRow.getByLabel("Game")).toHaveValue("Launch Test Game"); await expect(editGameRow.getByLabel("Game")).toHaveAttribute("readonly", ""); await editGameRow.getByLabel("Purpose").selectOption("Capability Demo"); @@ -525,11 +533,11 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page } try { await expect(page.locator("[data-game-row='demo-game'] [data-game-toggle='demo-game']")).not.toHaveClass(/primary/); await expect(page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Edit Demo Game" })).not.toHaveClass(/primary/); - await expect(page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Edit Demo Game" })).toBeDisabled(); + await expect(page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Edit Demo Game" })).toBeEnabled(); await expect(page.locator("[data-game-list]")).toContainText("Gravity Demo"); await expect(page.locator("[data-project-record-status]")).toHaveText("Game table loaded. Sign in to save changes."); await expect(page.locator("[data-project-records-table]")).toHaveCount(0); - await expect(page.getByRole("button", { name: "Add Game" })).toBeDisabled(); + await expect(page.getByRole("button", { name: "Add Game" })).toBeEnabled(); await expect(page.getByRole("button", { name: "Delete Open Game" })).toBeDisabled(); await expect(page.getByLabel("Game Name")).toHaveCount(0); await expect(page.getByLabel("Game Purpose")).toHaveCount(0); @@ -538,10 +546,21 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page } await page.locator("[data-game-row='gravity-demo'] [data-game-toggle='gravity-demo']").click(); await expect(page.locator("[data-game-row='gravity-demo'] [data-game-toggle='gravity-demo']")).not.toHaveClass(/primary/); - await expect(page.locator("[data-game-row='gravity-demo']").getByRole("button", { name: "Edit Gravity Demo" })).toBeDisabled(); + await expect(page.locator("[data-game-row='gravity-demo']").getByRole("button", { name: "Edit Gravity Demo" })).toBeEnabled(); await expect(page.locator("[data-game-hub-log]")).toHaveText("Sign in to create or update Game Hub projects."); - await expectNoPageFailures(failures); + await page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Edit Demo Game" }).click(); + await page.locator("[data-game-edit-row='demo-game']").getByRole("button", { name: "Save" }).click(); + await page.waitForURL(/\/account\/sign-in\.html$/); + + await page.goto(`${failures.server.baseUrl}/toolbox/game-hub/index.html`, { waitUntil: "networkidle" }); + await page.getByRole("button", { name: "Add Game" }).click(); + await page.locator("[data-game-add-row='input']").getByRole("button", { name: "Save" }).click(); + await page.waitForURL(/\/account\/sign-in\.html$/); + + expect(failures.pageErrors).toEqual([]); + expect(failures.consoleErrors).toEqual([]); + expect(failures.failedRequests.filter((request) => /^\d/.test(request) && !request.includes("/account/sign-in.html"))).toEqual([]); } finally { await failures.server.close(); } @@ -599,7 +618,6 @@ test("Game Hub shows a creator-safe empty state when no projects exist", async ( "Game", "Purpose", "Status", - "Owner", "Actions", ]); await expect(page.locator("[data-game-list] [data-game-row]")).toHaveCount(0); @@ -687,7 +705,7 @@ test("Game Hub reports malformed active-game payloads without throwing", async ( await expect(page.locator("[data-active-game-name]")).toHaveCount(0); await expect(page.locator("[data-current-user-role]")).toHaveCount(0); await expect(page.locator("[data-game-hub-log]")).toContainText("Active game is temporarily unavailable."); - await expect(page.getByRole("button", { name: "Add Game" })).toBeDisabled(); + await expect(page.getByRole("button", { name: "Add Game" })).toBeEnabled(); await expectNoPageFailures(failures); } finally { diff --git a/toolbox/game-hub/game-hub.js b/toolbox/game-hub/game-hub.js index 888c75524..e4659f47b 100644 --- a/toolbox/game-hub/game-hub.js +++ b/toolbox/game-hub/game-hub.js @@ -158,6 +158,18 @@ function ensureProjectRecordsSaveAllowed(action) { return false; } +function redirectGuestToSignIn() { + window.location.href = "account/sign-in.html"; +} + +function ensureProjectRecordsSaveAllowedForSave() { + if (projectRecordsSaveAllowed()) { + return true; + } + redirectGuestToSignIn(); + return false; +} + function populateSelect(select, options) { if (!select) { return; @@ -188,7 +200,7 @@ function currentGameMember(activeGame) { function createActionButton(label, action, options = {}) { const button = document.createElement("button"); - button.className = options.primary ? "btn primary" : "btn"; + button.className = options.primary ? "btn btn--compact primary" : "btn btn--compact"; button.type = "button"; button.dataset.gameAction = action; if (options.gameId) { @@ -207,7 +219,6 @@ function createActionButton(label, action, options = {}) { function createGameButton(game) { const button = createActionButton("Edit", "edit-game", { ariaLabel: `Edit ${game.name}`, - disabled: !projectRecordsSaveAllowed(), gameId: game.id, }); return button; @@ -381,7 +392,7 @@ function renderExpandedGameRow(tbody, game, progress, active) { row.dataset.gameChildRow = type; row.id = id; const content = document.createElement("td"); - content.colSpan = 5; + content.colSpan = 4; render(content); row.append(content); tbody.append(row); @@ -394,10 +405,8 @@ function renderAddGameRow(tbody) { if (!state.addingGame) { const cell = document.createElement("td"); - cell.colSpan = 5; - cell.append(createActionButton("Add Game", "start-add-game", { - disabled: !projectRecordsSaveAllowed(), - })); + cell.colSpan = 4; + cell.append(createActionButton("Add Game", "start-add-game")); row.append(cell); tbody.append(row); return; @@ -415,14 +424,13 @@ function renderAddGameRow(tbody) { const statusCell = document.createElement("td"); statusCell.append(createSelect(GAME_HUB_GAME_STATUSES, "Planning", "gameStatusInput", "Status")); - const ownerCell = createCell("Current user"); const actions = document.createElement("td"); actions.append( createActionButton("Save", "save-add-game", { primary: true }), createActionButton("Cancel", "cancel-add-game"), ); - row.append(nameCell, purposeCell, statusCell, ownerCell, actions); + row.append(nameCell, purposeCell, statusCell, actions); tbody.append(row); } @@ -457,7 +465,6 @@ function renderEditGameRow(tbody, game) { nameCell, purposeCell, statusCell, - createCell(game.ownerDisplayName || "No owner"), actions, ); tbody.append(row); @@ -490,7 +497,6 @@ function renderGameParentRow(tbody, game, activeGame, progress) { nameCell, createCell(game.purpose || "Game"), createCell(game.status || "No status"), - createCell(game.ownerDisplayName || "No owner"), ); const actions = document.createElement("td"); @@ -538,7 +544,7 @@ function renderGameList(progress) { table.className = "data-table data-table--fixed"; table.dataset.gameRowsTable = "true"; table.setAttribute("aria-label", "Games"); - table.innerHTML = "GamePurposeStatusOwnerActions"; + table.innerHTML = "GamePurposeStatusActions"; const body = document.createElement("tbody"); listResult.forEach((game) => renderGameParentRow(body, game, activeGame, progress)); renderAddGameRow(body); @@ -652,7 +658,7 @@ function readGameRowFields(row) { } function saveAddedGame(row) { - if (!ensureProjectRecordsSaveAllowed("create")) { + if (!ensureProjectRecordsSaveAllowedForSave()) { return; } const input = readGameRowFields(row); @@ -677,7 +683,7 @@ function saveAddedGame(row) { } function saveEditedGame(row, gameId) { - if (!ensureProjectRecordsSaveAllowed("update")) { + if (!ensureProjectRecordsSaveAllowedForSave()) { return; } const input = readGameRowFields(row); @@ -740,9 +746,6 @@ elements.gameList?.addEventListener("click", (event) => { } if (action.dataset.gameAction === "start-add-game") { - if (!ensureProjectRecordsSaveAllowed("create")) { - return; - } state.addingGame = true; state.editingGameId = ""; renderWorkspace(); @@ -762,9 +765,6 @@ elements.gameList?.addEventListener("click", (event) => { } if (action.dataset.gameAction === "edit-game") { - if (!ensureProjectRecordsSaveAllowed("update")) { - return; - } const game = repository.openGame(action.dataset.gameId); if (reportRepositoryError(game, "Edit game") || !isRecord(game)) { if (!isRepositoryErrorResult(game)) { diff --git a/toolbox/game-hub/index.html b/toolbox/game-hub/index.html index 8306e6366..c313eeb7a 100644 --- a/toolbox/game-hub/index.html +++ b/toolbox/game-hub/index.html @@ -26,16 +26,20 @@

Game Hub

Games

-

Review games in the parent table, then expand a game row to see Source Idea and Readiness Output.

Game table ready.
Game Hub ready.