From 50fe81c6c0bc325a98d9b15433647696aa9f6c98 Mon Sep 17 00:00:00 2001 From: DavidQ Date: Mon, 22 Jun 2026 21:18:06 -0400 Subject: [PATCH] PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup --- ...17-game-hub-guest-save-and-crew-cleanup.md | 25 +++++++++++ ...ave-and-crew-cleanup_branch-validation.txt | 14 ++++++ ...d-crew-cleanup_manual-validation-notes.txt | 9 ++++ ...and-crew-cleanup_requirement-checklist.txt | 16 +++++++ ...-save-and-crew-cleanup_validation-lane.txt | 12 ++++++ .../dev/reports/codex_changed_files.txt | 9 +++- docs_build/dev/reports/codex_review.diff | Bin 9100 -> 34398 bytes .../tools/GameHubMockRepository.spec.mjs | 38 ++++++++++++----- toolbox/game-hub/game-hub.js | 40 +++++++++--------- toolbox/game-hub/index.html | 14 +++--- 10 files changed, 141 insertions(+), 36 deletions(-) create mode 100644 docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup.md create mode 100644 docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_branch-validation.txt create mode 100644 docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_manual-validation-notes.txt create mode 100644 docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_requirement-checklist.txt create mode 100644 docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_validation-lane.txt 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 f6a9821e07e08c651be555e61a6cb706a3f3c578..882ec504bd38b66fedc5a565d72cc27566817418 100644 GIT binary patch literal 34398 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 { 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.